dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.5k stars 3.13k forks source link

model.GetEntityTypes() is empty inside overridden Generate method with DropTableOperation of custom SqlServerMigrationsSqlGenerator #30318

Closed veenadange closed 1 year ago

veenadange commented 1 year ago

Hi Team,

I am trying to override couple of operations from SqlServerMigrationsSqlGenerator in my custom migration generator. I need to have access to the table entity for which CreateTableOperation/DropTableOperation is invoked to read a custom annotation.

It works with below code when Generate method for CreateTableOperation gets invoked but when Generate method for DropTableOperation is called (while executing rollback migration), model.GetEntityTypes() returns empty.

bool enableRlsPolicy = model?.GetEntityTypes()?.FirstOrDefault(
      x => (x.GetTableName()??string.Empty).Equals(createTableOperation.Name))?.FindAnnotation(new RlsPolicy().Name) != null;

Please let me know any solutions to this.

Below is the detailed code snippet -

internal class ExtendedSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
        public ExtendedSqlServerMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, ICommandBatchPreparer migrationsAnnotations)
            : base(dependencies, migrationsAnnotations) {
        }

        protected override void Generate(
            DropTableOperation dropTableOperation, IModel? model, MigrationCommandListBuilder builder, bool terminate)
        {
            Debugger.Launch();
            //if table has RLS data annotation drop filter/block predicate for it from security policy
            bool enableRlsPolicy = model?.GetEntityTypes()?.FirstOrDefault(x => (x.GetTableName() ?? string.Empty).Equals(dropTableOperation.Name))?.FindAnnotation(new RlsPolicy().Name) != null;

//some custom logic
            base.Generate(dropTableOperation, model, builder, true);
        }
}
veenadange commented 1 year ago

Hi Team, waiting for any update. Thanks!

ajcvickers commented 1 year ago

@veenadange I don't know what the expected behavior is here, so this will need to wait until I can talk to the team about it. Following that, it may still need further investigation.

veenadange commented 1 year ago

Sure @ajcvickers ! No problem. I was just expecting to be able to read entity related information in 'DropTableOperation' override similar to what is working correctly for 'CreateTableOperation' override.

ajcvickers commented 1 year ago

@veenadange I am not able to reproduce this--see my code below. I first create two migrations and update the database to the latest:

PS C:\local\code\AllTogetherNow\Daily> dotnet ef migrations add One
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PS C:\local\code\AllTogetherNow\Daily> dotnet ef migrations add Two
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PS C:\local\code\AllTogetherNow\Daily> dotnet ef database update One
Build started...
Build succeeded.
Applying migration '20230308164821_One'.
Done.
PS C:\local\code\AllTogetherNow\Daily> dotnet ef database update Two
Build started...
Build succeeded.
Applying migration '20230308164841_Two'.
Done.

If I then go back down to the first migration, I see the table in the target model:

PS C:\local\code\AllTogetherNow\Daily> dotnet ef database update One
Build started...
Build succeeded.
Reverting migration '20230308164841_Two'.
>>>>>
Entity types in target model:
  EntityType: Entity1 (Dictionary<string, object>) CLR Type: Dictionary<string, object>
    Properties:
      Id (no field, int) Indexer Required PK AfterSave:Throw ValueGenerated.OnAdd
    Keys:
      Id PK
<<<<<
Done.

If I now go down to the empty database, then the target model is null, as expected:

PS C:\local\code\AllTogetherNow\Daily> dotnet ef database update 0  
Build started...
Build succeeded.
Reverting migration '20230308164821_One'.
>>>>>
No target model.
<<<<<
Done.

At no time does the target model have no entity types.

My code:

using (var context = new SomeDbContext())
{
    context.Database.EnsureDeleted();
    //context.Database.EnsureCreated();
}

public class Entity1
{
    public int Id { get; set; }
}

public class Entity2
{
    public int Id { get; set; }
}

public class SomeDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(@"Data Source=(LocalDb)\MSSQLLocalDB;Database=AllTogetherNow")
            .ReplaceService<IMigrationsSqlGenerator, ExtendedSqlServerMigrationsSqlGenerator>()
            // .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Entity1>();
        modelBuilder.Entity<Entity2>();
    }
}

public class ExtendedSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
    public ExtendedSqlServerMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, ICommandBatchPreparer migrationsAnnotations)
        : base(dependencies, migrationsAnnotations) {
    }

    protected override void Generate(
        DropTableOperation dropTableOperation, IModel? model, MigrationCommandListBuilder builder, bool terminate)
    {
        Console.WriteLine(">>>>>");
        if (model == null)
        {
            Console.WriteLine("No target model.");
        }
        else
        {
            Console.WriteLine("Entity types in target model:");
            foreach (var entityType in model.GetEntityTypes())
            {
                Console.WriteLine(entityType.ToDebugString(indent: 2));
            }
        }
        Console.WriteLine("<<<<<");

        base.Generate(dropTableOperation, model, builder, true);
    }
}