From d2e075078ccb8834a3e0bdd55b39428845428a24 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 16 Jun 2022 19:55:34 +0200 Subject: [PATCH] "Scaffold" triggers for SQL Server HasTrigger with trigger name only, to make the SQL Server SaveChanges work out of the box. Closes #28185 --- .../Internal/CSharpDbContextGenerator.cs | 37 +++++++ .../RelationalScaffoldingModelFactory.cs | 10 ++ .../Internal/SqlServerDatabaseModelFactory.cs | 43 ++++++++ .../Internal/CSharpDbContextGeneratorTest.cs | 82 ++++++++++++++ .../SqlServerDatabaseModelFactoryTest.cs | 100 ++++++++++++++---- .../SqlServerQueryTriggersTest.cs | 2 - .../SqlServerAnnotationCodeGeneratorTest.cs | 32 +++--- 7 files changed, 266 insertions(+), 40 deletions(-) diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 57459df310e..41cbee2abbb 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -424,6 +424,43 @@ private void GenerateEntityType(IEntityType entityType) GenerateManyToMany(skipNavigation); } } + + var triggers = entityType.GetTriggers().ToArray(); + + if (triggers.Length > 0) + { + using (_builder.Indent()) + { + _builder.AppendLine(); + + _builder.Append($"{EntityLambdaIdentifier}.{nameof(RelationalEntityTypeBuilderExtensions.ToTable)}(tb => "); + + // Note: no trigger annotation support as of yet + + if (triggers.Length == 1) + { + var trigger = triggers[0]; + if (trigger.Name is not null) + { + _builder.AppendLine($"tb.HasTrigger({_code.Literal(trigger.Name)}));"); + } + } + else + { + _builder.AppendLine("{"); + + using (_builder.Indent()) + { + foreach (var trigger in entityType.GetTriggers().Where(t => t.Name is not null)) + { + _builder.AppendLine($"tb.HasTrigger({_code.Literal(trigger.Name!)});"); + } + } + + _builder.AppendLine("});"); + } + } + } } private void AppendMultiLineFluentApi(IEntityType entityType, IList lines) diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index 2ce3b6ab19e..04a2c4b2f59 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -337,6 +337,16 @@ protected virtual ModelBuilder VisitTables(ModelBuilder modelBuilder, ICollectio VisitUniqueConstraints(builder, table.UniqueConstraints); VisitIndexes(builder, table.Indexes); + if (table.FindAnnotation(RelationalAnnotationNames.Triggers) is { Value: HashSet triggers }) + { + foreach (var triggerName in triggers) + { + builder.ToTable(table.Name, table.Schema, tb => tb.HasTrigger(triggerName)); + } + + table.RemoveAnnotation(RelationalAnnotationNames.Triggers); + } + builder.Metadata.AddAnnotations(table.GetAnnotations()); return builder; diff --git a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs index 71c4e39fb73..4ab10bd3564 100644 --- a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs +++ b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs @@ -648,6 +648,7 @@ FROM [sys].[views] AS [v] GetColumns(connection, tables, filter, viewFilter, typeAliases, databaseCollation); GetIndexes(connection, tables, filter); GetForeignKeys(connection, tables, filter); + GetTriggers(connection, tables, filter); foreach (var table in tables) { @@ -1295,6 +1296,48 @@ FROM [sys].[foreign_keys] AS [f] } } + private void GetTriggers(DbConnection connection, IReadOnlyList tables, string tableFilter) + { + using var command = connection.CreateCommand(); + command.CommandText = @" +SELECT + SCHEMA_NAME([t].[schema_id]) AS [table_schema], + [t].[name] AS [table_name], + [tr].[name] AS [trigger_name] +FROM [sys].[triggers] AS [tr] +JOIN [sys].[tables] AS [t] ON [tr].[parent_id] = [t].[object_id] +WHERE " + + tableFilter + + @" +ORDER BY [table_schema], [table_name], [tr].[name]"; + + using var reader = command.ExecuteReader(); + var tableGroups = reader.Cast() + .GroupBy( + ddr => (tableSchema: ddr.GetValueOrDefault("table_schema"), + tableName: ddr.GetFieldValue("table_name"))); + + foreach (var tableGroup in tableGroups) + { + var tableSchema = tableGroup.Key.tableSchema; + var tableName = tableGroup.Key.tableName; + + var table = tables.Single(t => t.Schema == tableSchema && t.Name == tableName); + + var triggers = new HashSet(); + table[RelationalAnnotationNames.Triggers] = triggers; + + foreach (var triggerRecord in tableGroup) + { + var triggerName = triggerRecord.GetFieldValue("trigger_name"); + + // We don't actually scaffold anything beyond the fact that there's a trigger with a given name. + // This is to modify the SaveChanges logic to not use OUTPUT without INTO, which is incompatible with triggers. + triggers.Add(triggerName); + } + } + } + private bool SupportsTemporalTable() => _compatibilityLevel >= 130 && _engineEdition != 6; diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs index 3b8eaf0a1ba..9f88365b5a0 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs @@ -1195,6 +1195,88 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // TODO })).Message); + [ConditionalFact] + public void Trigger_works() + => Test( + modelBuilder => modelBuilder + .Entity( + "Employee", + x => + { + x.Property("Id"); + x.ToTable( + tb => + { + tb.HasTrigger("Trigger1"); + tb.HasTrigger("Trigger2"); + }); + }), + new ModelCodeGenerationOptions { UseDataAnnotations = false }, + code => + { + AssertFileContents( + @"using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace TestNamespace +{ + public partial class TestDbContext : DbContext + { + public TestDbContext() + { + } + + public TestDbContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Employee { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (!optionsBuilder.IsConfigured) + { +#warning " + + DesignStrings.SensitiveInformationWarning + + @" + optionsBuilder.UseSqlServer(""Initial Catalog=TestDatabase""); + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.Property(e => e.Id).UseIdentityColumn(); + + entity.ToTable(tb => { + tb.HasTrigger(""Trigger1""); + tb.HasTrigger(""Trigger2""); + }); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); + } +} +", + code.ContextFile); + }, + model => + { + var entityType = model.FindEntityType("TestNamespace.Employee")!; + var triggers = entityType.GetTriggers(); + + Assert.Collection(triggers.OrderBy(t => t.Name), + t => Assert.Equal("Trigger1", t.Name), + t => Assert.Equal("Trigger2", t.Name)); + }); + protected override void AddModelServices(IServiceCollection services) => services.Replace(ServiceDescriptor.Singleton()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs index 9f7a531df54..7ecdc757ef7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs @@ -11,6 +11,8 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding; +#nullable enable + public class SqlServerDatabaseModelFactoryTest : IClassFixture { protected SqlServerDatabaseModelFixture Fixture { get; } @@ -633,7 +635,7 @@ CONSTRAINT [PK_Blogs] PRIMARY KEY NONCLUSTERED ([Id]) var table = Assert.Single(dbModel.Tables.Where(t => t.Name == "Blogs")); // ReSharper disable once PossibleNullReferenceException - Assert.True((bool)table[SqlServerAnnotationNames.MemoryOptimized]); + Assert.True((bool)table[SqlServerAnnotationNames.MemoryOptimized]!); }, "DROP TABLE [Blogs]"); @@ -720,7 +722,7 @@ Id int PRIMARY KEY { var pk = dbModel.Tables.Single().PrimaryKey; - Assert.Equal("dbo", pk.Table.Schema); + Assert.Equal("dbo", pk!.Table!.Schema); Assert.Equal("PrimaryKeyTable", pk.Table.Name); Assert.StartsWith("PK__PrimaryK", pk.Name); Assert.Null(pk[SqlServerAnnotationNames.Clustered]); @@ -778,7 +780,7 @@ CREATE TABLE IndexTable ( Assert.All( table.Indexes, c => { - Assert.Equal("dbo", c.Table.Schema); + Assert.Equal("dbo", c.Table!.Schema); Assert.Equal("IndexTable", c.Table.Name); }); @@ -808,7 +810,7 @@ IndexProperty int Assert.All( table.Indexes, c => { - Assert.Equal("dbo", c.Table.Schema); + Assert.Equal("dbo", c.Table!.Schema); Assert.Equal("IndexTable", c.Table.Name); }); @@ -880,6 +882,45 @@ FOREIGN KEY (Id) REFERENCES PrincipalTable(Id) ON DELETE NO ACTION, DROP TABLE FirstDependent; DROP TABLE PrincipalTable;"); + [ConditionalFact] + public void Triggers() + => Test( + new[] { + @" +CREATE TABLE SomeTable ( + Id int IDENTITY PRIMARY KEY, + Foo int, + Bar int, + Baz int +);", + @" +CREATE TRIGGER Trigger1 + ON SomeTable + AFTER INSERT AS +BEGIN + UPDATE SomeTable SET Bar=Foo WHERE Id IN (SELECT INSERTED.Id FROM INSERTED); +END;", + @" +CREATE TRIGGER Trigger2 + ON SomeTable + AFTER INSERT AS +BEGIN + UPDATE SomeTable SET Baz=Foo WHERE Id IN (SELECT INSERTED.Id FROM INSERTED); +END;" }, + Enumerable.Empty(), + Enumerable.Empty(), + dbModel => + { + var table = dbModel.Tables.Single(); + var triggers = (HashSet)table[RelationalAnnotationNames.Triggers]!; + + Assert.Collection(triggers.OrderBy(t => t), + t => Assert.Equal("Trigger1", t), + t => Assert.Equal("Trigger2", t)); + + }, + "DROP TABLE SomeTable;"); + #endregion #region ColumnFacets @@ -1480,7 +1521,7 @@ CREATE TABLE RowVersionTable ( { var columns = dbModel.Tables.Single().Columns; - Assert.True((bool)columns.Single(c => c.Name == "rowversionColumn")[ScaffoldingAnnotationNames.ConcurrencyToken]); + Assert.True((bool)columns.Single(c => c.Name == "rowversionColumn")[ScaffoldingAnnotationNames.ConcurrencyToken]!); }, "DROP TABLE RowVersionTable;"); @@ -1539,7 +1580,7 @@ NonSparse nvarchar(max) NULL { var columns = dbModel.Tables.Single().Columns; - Assert.True((bool)columns.Single(c => c.Name == "Sparse")[SqlServerAnnotationNames.Sparse]); + Assert.True((bool)columns.Single(c => c.Name == "Sparse")[SqlServerAnnotationNames.Sparse]!); Assert.Null(columns.Single(c => c.Name == "NonSparse")[SqlServerAnnotationNames.Sparse]); }, "DROP TABLE ColumnsWithSparseness;"); @@ -1633,7 +1674,7 @@ PRIMARY KEY (Id2, Id1) { var pk = dbModel.Tables.Single().PrimaryKey; - Assert.Equal("dbo", pk.Table.Schema); + Assert.Equal("dbo", pk!.Table!.Schema); Assert.Equal("CompositePrimaryKeyTable", pk.Table.Name); Assert.StartsWith("PK__Composit", pk.Name); Assert.Equal( @@ -1655,10 +1696,10 @@ CREATE TABLE NonClusteredPrimaryKeyTable ( { var pk = dbModel.Tables.Single().PrimaryKey; - Assert.Equal("dbo", pk.Table.Schema); + Assert.Equal("dbo", pk!.Table!.Schema); Assert.Equal("NonClusteredPrimaryKeyTable", pk.Table.Name); Assert.StartsWith("PK__NonClust", pk.Name); - Assert.False((bool)pk[SqlServerAnnotationNames.Clustered]); + Assert.False((bool)pk[SqlServerAnnotationNames.Clustered]!); Assert.Equal( new List { "Id1" }, pk.Columns.Select(ic => ic.Name).ToList()); }, @@ -1680,10 +1721,10 @@ CREATE TABLE NonClusteredPrimaryKeyTableWithClusteredIndex ( { var pk = dbModel.Tables.Single().PrimaryKey; - Assert.Equal("dbo", pk.Table.Schema); + Assert.Equal("dbo", pk!.Table!.Schema); Assert.Equal("NonClusteredPrimaryKeyTableWithClusteredIndex", pk.Table.Name); Assert.StartsWith("PK__NonClust", pk.Name); - Assert.False((bool)pk[SqlServerAnnotationNames.Clustered]); + Assert.False((bool)pk[SqlServerAnnotationNames.Clustered]!); Assert.Equal( new List { "Id1" }, pk.Columns.Select(ic => ic.Name).ToList()); }, @@ -1704,10 +1745,10 @@ CONSTRAINT UK_Clustered UNIQUE CLUSTERED ( Id2 ), { var pk = dbModel.Tables.Single().PrimaryKey; - Assert.Equal("dbo", pk.Table.Schema); + Assert.Equal("dbo", pk!.Table!.Schema); Assert.Equal("NonClusteredPrimaryKeyTableWithClusteredConstraint", pk.Table.Name); Assert.StartsWith("PK__NonClust", pk.Name); - Assert.False((bool)pk[SqlServerAnnotationNames.Clustered]); + Assert.False((bool)pk[SqlServerAnnotationNames.Clustered]!); Assert.Equal( new List { "Id1" }, pk.Columns.Select(ic => ic.Name).ToList()); }, @@ -1728,7 +1769,7 @@ CONSTRAINT MyPK PRIMARY KEY ( Id2 ), { var pk = dbModel.Tables.Single().PrimaryKey; - Assert.Equal("dbo", pk.Table.Schema); + Assert.Equal("dbo", pk!.Table!.Schema); Assert.Equal("PrimaryKeyName", pk.Table.Name); Assert.StartsWith("MyPK", pk.Name); Assert.Null(pk[SqlServerAnnotationNames.Clustered]); @@ -1783,7 +1824,7 @@ CREATE TABLE ClusteredUniqueConstraintTable ( Assert.Equal("dbo", uniqueConstraint.Table.Schema); Assert.Equal("ClusteredUniqueConstraintTable", uniqueConstraint.Table.Name); Assert.StartsWith("UQ__Clustere", uniqueConstraint.Name); - Assert.True((bool)uniqueConstraint[SqlServerAnnotationNames.Clustered]); + Assert.True((bool)uniqueConstraint[SqlServerAnnotationNames.Clustered]!); Assert.Equal( new List { "Id2" }, uniqueConstraint.Columns.Select(ic => ic.Name).ToList()); }, @@ -1834,7 +1875,7 @@ CREATE TABLE CompositeIndexTable ( var index = Assert.Single(dbModel.Tables.Single().Indexes); // ReSharper disable once PossibleNullReferenceException - Assert.Equal("dbo", index.Table.Schema); + Assert.Equal("dbo", index.Table!.Schema); Assert.Equal("CompositeIndexTable", index.Table.Name); Assert.Equal("IX_COMPOSITE", index.Name); Assert.Equal( @@ -1859,10 +1900,10 @@ CREATE TABLE ClusteredIndexTable ( var index = Assert.Single(dbModel.Tables.Single().Indexes); // ReSharper disable once PossibleNullReferenceException - Assert.Equal("dbo", index.Table.Schema); + Assert.Equal("dbo", index.Table!.Schema); Assert.Equal("ClusteredIndexTable", index.Table.Name); Assert.Equal("IX_CLUSTERED", index.Name); - Assert.True((bool)index[SqlServerAnnotationNames.Clustered]); + Assert.True((bool)index[SqlServerAnnotationNames.Clustered]!); Assert.Equal( new List { "Id2" }, index.Columns.Select(ic => ic.Name).ToList()); }, @@ -1885,7 +1926,7 @@ CREATE TABLE UniqueIndexTable ( var index = Assert.Single(dbModel.Tables.Single().Indexes); // ReSharper disable once PossibleNullReferenceException - Assert.Equal("dbo", index.Table.Schema); + Assert.Equal("dbo", index.Table!.Schema); Assert.Equal("UniqueIndexTable", index.Table.Name); Assert.Equal("IX_UNIQUE", index.Name); Assert.True(index.IsUnique); @@ -1912,7 +1953,7 @@ CREATE TABLE FilteredIndexTable ( var index = Assert.Single(dbModel.Tables.Single().Indexes); // ReSharper disable once PossibleNullReferenceException - Assert.Equal("dbo", index.Table.Schema); + Assert.Equal("dbo", index.Table!.Schema); Assert.Equal("FilteredIndexTable", index.Table.Name); Assert.Equal("IX_UNIQUE", index.Name); Assert.Equal("([Id2]>(10))", index.Filter); @@ -2368,13 +2409,26 @@ public void No_warning_missing_view_definition() #endregion private void Test( - string createSql, + string? createSql, + IEnumerable tables, + IEnumerable schemas, + Action asserter, + string? cleanupSql) + => Test( + string.IsNullOrEmpty(createSql) ? Array.Empty() : new[] { createSql }, + tables, + schemas, + asserter, + cleanupSql); + + private void Test( + string[] createSqls, IEnumerable tables, IEnumerable schemas, Action asserter, - string cleanupSql) + string? cleanupSql) { - if (!string.IsNullOrEmpty(createSql)) + foreach (var createSql in createSqls) { Fixture.TestStore.ExecuteNonQuery(createSql); } diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerQueryTriggersTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerQueryTriggersTest.cs index 24ed01dee06..ce5fd9f52c6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerQueryTriggersTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerQueryTriggersTest.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - - // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore; diff --git a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs index 5eb8dd6bf7e..4d45e99c49b 100644 --- a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs @@ -6,6 +6,8 @@ namespace Microsoft.EntityFrameworkCore.Design.Internal; +#nullable enable + public class SqlServerAnnotationCodeGeneratorTest { [ConditionalFact] @@ -21,7 +23,7 @@ public void GenerateFluentApi_IKey_works_when_clustered() x.Property("Id"); x.HasKey("Id").IsClustered(); }); - var key = (IKey)modelBuilder.Model.FindEntityType("Post").GetKeys().Single(); + var key = (IKey)modelBuilder.Model.FindEntityType("Post")!.GetKeys().Single(); var result = generator.GenerateFluentApiCalls(key, key.GetAnnotations().ToDictionary(a => a.Name, a => a)) .Single(); @@ -43,7 +45,7 @@ public void GenerateFluentApi_IKey_works_when_nonclustered() x.Property("Id"); x.HasKey("Id").IsClustered(false); }); - var key = (IKey)modelBuilder.Model.FindEntityType("Post").GetKeys().Single(); + var key = (IKey)modelBuilder.Model.FindEntityType("Post")!.GetKeys().Single(); var result = generator.GenerateFluentApiCalls(key, key.GetAnnotations().ToDictionary(a => a.Name, a => a)) .Single(); @@ -67,7 +69,7 @@ public void GenerateFluentApi_IIndex_works_when_clustered() x.Property("Name"); x.HasIndex("Name").IsClustered(); }); - var index = (IIndex)modelBuilder.Model.FindEntityType("Post").GetIndexes().Single(); + var index = (IIndex)modelBuilder.Model.FindEntityType("Post")!.GetIndexes().Single(); var result = generator.GenerateFluentApiCalls(index, index.GetAnnotations().ToDictionary(a => a.Name, a => a)) .Single(); @@ -90,7 +92,7 @@ public void GenerateFluentApi_IIndex_works_when_nonclustered() x.Property("Name"); x.HasIndex("Name").IsClustered(false); }); - var index = (IIndex)modelBuilder.Model.FindEntityType("Post").GetIndexes().Single(); + var index = (IIndex)modelBuilder.Model.FindEntityType("Post")!.GetIndexes().Single(); var result = generator.GenerateFluentApiCalls(index, index.GetAnnotations().ToDictionary(a => a.Name, a => a)) .Single(); @@ -115,7 +117,7 @@ public void GenerateFluentApi_IIndex_works_with_fillfactor() x.HasIndex("Name").HasFillFactor(90); }); - var index = (IIndex)modelBuilder.Model.FindEntityType("Post").GetIndexes().Single(); + var index = (IIndex)modelBuilder.Model.FindEntityType("Post")!.GetIndexes().Single(); var result = generator.GenerateFluentApiCalls(index, index.GetAnnotations().ToDictionary(a => a.Name, a => a)) .Single(); @@ -139,7 +141,7 @@ public void GenerateFluentApi_IIndex_works_with_includes() x.HasIndex("LastName").IncludeProperties("FirstName"); }); - var index = (IIndex)modelBuilder.Model.FindEntityType("Post").GetIndexes().Single(); + var index = (IIndex)modelBuilder.Model.FindEntityType("Post")!.GetIndexes().Single(); var result = generator.GenerateFluentApiCalls(index, index.GetAnnotations().ToDictionary(a => a.Name, a => a)) .Single(); @@ -175,7 +177,7 @@ public void GenerateFluentApi_IProperty_works_with_identity() var generator = CreateGenerator(); var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder(); modelBuilder.Entity("Post", x => x.Property("Id").UseIdentityColumn(5, 10)); - var property = modelBuilder.Model.FindEntityType("Post").FindProperty("Id"); + var property = modelBuilder.Model.FindEntityType("Post")!.FindProperty("Id")!; var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); var result = generator.GenerateFluentApiCalls((IProperty)property, annotations).Single(); @@ -195,7 +197,7 @@ public void GenerateFluentApi_IProperty_works_with_identity_default_seed_increme var generator = CreateGenerator(); var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder(); modelBuilder.Entity("Post", x => x.Property("Id").UseIdentityColumn()); - var property = modelBuilder.Model.FindEntityType("Post").FindProperty("Id"); + var property = modelBuilder.Model.FindEntityType("Post")!.FindProperty("Id")!; var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); var result = generator.GenerateFluentApiCalls((IProperty)property, annotations).Single(); @@ -234,7 +236,7 @@ public void GenerateFluentApi_IProperty_works_with_HiLo() var generator = CreateGenerator(); var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder(); modelBuilder.Entity("Post", x => x.Property("Id").UseHiLo("HiLoIndexName", "HiLoIndexSchema")); - var property = modelBuilder.Model.FindEntityType("Post").FindProperty("Id"); + var property = modelBuilder.Model.FindEntityType("Post")!.FindProperty("Id")!; var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); var result = generator.GenerateFluentApiCalls((IProperty)property, annotations).Single(); @@ -263,17 +265,17 @@ public void GenerateFluentApi_IProperty_works_with_IsSparse() Assert.Null(GenerateFluentApiCall("SomeEntity", "Default")); - var sparseCall = GenerateFluentApiCall("SomeEntity", "Sparse"); + var sparseCall = GenerateFluentApiCall("SomeEntity", "Sparse")!; Assert.Equal("IsSparse", sparseCall.Method); Assert.Empty(sparseCall.Arguments); - var nonSparseCall = GenerateFluentApiCall("SomeEntity", "NonSparse"); + var nonSparseCall = GenerateFluentApiCall("SomeEntity", "NonSparse")!; Assert.Equal("IsSparse", nonSparseCall.Method); - Assert.Collection(nonSparseCall.Arguments, o => Assert.False((bool)o)); + Assert.Collection(nonSparseCall.Arguments, o => Assert.False((bool)o!)); - MethodCallCodeFragment GenerateFluentApiCall(string entityTypeName, string propertyName) + MethodCallCodeFragment? GenerateFluentApiCall(string entityTypeName, string propertyName) { - var property = modelBuilder.Model.FindEntityType(entityTypeName).FindProperty(propertyName); + var property = modelBuilder.Model.FindEntityType(entityTypeName)!.FindProperty(propertyName)!; var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); return generator.GenerateFluentApiCalls((IProperty)property, annotations).SingleOrDefault(); } @@ -334,7 +336,7 @@ public void GenerateFluentApi_IEntityType_works_when_IsMemoryOptimized() x.Property("Id"); x.IsMemoryOptimized(); }); - var entityType = (IEntityType)modelBuilder.Model.FindEntityType("Post"); + var entityType = (IEntityType)modelBuilder.Model.FindEntityType("Post")!; var result = generator.GenerateFluentApiCalls(entityType, entityType.GetAnnotations().ToDictionary(a => a.Name, a => a)) .Single();