8

My goal is to create a custom Attribute and allow. Add-Migration to have custom code generated based on it.

The Model and Attribute class

public class MyAttribute: Attribute {} public class MyModel { public int Id { get; set; } [MyAttribute] public string Name { get; set; } } 

The MigrationProvider and AnnotationProvider:

internal class MyMigrationsAnnotationProvider : SqliteMigrationsAnnotationProvider { public override IEnumerable<IAnnotation> For( IProperty property ) { MemberInfo MInfo = property.PropertyInfo ?? ( MemberInfo ) property.FieldInfo; MyAttribute MyAttr = MInfo?.GetCustomAttribute<MyAttribute>(); if ( MyAttr != null ) { return base.For( property ).Concat( new IAnnotation[] { new Annotation( "MyAttribute", true ) } ); } return base.For( property ); } } internal class MyMigrationsSqlGenerator : SqliteMigrationsSqlGenerator { public MyMigrationsSqlGenerator( IRelationalCommandBuilderFactory IRFactory, ISqlGenerationHelper ISHelper, IRelationalTypeMapper Mapper, IRelationalAnnotationProvider AnProvider ) : base( IRFactory, ISHelper, Mapper, AnProvider ) {} protected override void Generate( AddColumnOperation operation, IModel model, MigrationCommandListBuilder builder ) { throw new Exception( "Hello world" ); // Here's where I got it wrong. // I thought I should be able to read the "MyAttribute" annotation from here and generate extra code in the Up method /* if( operation.FindAnnotation( "MyAttribute" ) != null ) { builder.AppendLine( "Hello there, not sure if this would work." ); } */ } } class MyContext : DbContext { public DbSet<MyModel> MModel { get; set; } protected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder ) { optionsBuilder.UseSqlite( "Data Source=mydata.db" ); optionsBuilder.ReplaceService<IMigrationsSqlGenerator, MyMigrationsSqlGenerator>(); optionsBuilder.ReplaceService<IMigrationsAnnotationProvider, MyMigrationsAnnotationProvider>(); } } 

Generated Migration codes ( with some clean up )

protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "MyModel", columns: table => new { Id = table.Column<string>(nullable: false), Name = table.Column<string>(nullable: false) .Annotation("MyAttribute", true), }); // The following line is what I want it to be generated migrationBuilder.Sql( "I WANT MY CUSTOM QUERY BE GENERATED HERE" ); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "MyModel" ); // The following line is what I want it to be generated migrationBuilder.Sql( "I WANT MY CUSTOM QUERY BE GENERATED HERE" ); } 

As you can see the MyAttribute annotation is successfully added to the Up method. However, I couldn't seem to override the Generate method since there's no Hello world exception throw while running Add-Migration.

I am using EF Core 1.1.5

Thanks in advance!

2 Answers 2

1

The IMigrationsSqlGenerator can only process a MigrationOperation that has been generated. To detect changes in your new Attribute, you will probably need to replace the IMigrationsModelDiffer service. Then you can return a new SqlOperation (or custom type) with the other differences between the two models.

On the plus side, this means that you can generate undo operations in the Down process too.

Sign up to request clarification or add additional context in comments.

4 Comments

This is probably it. Could you provide some examples to fiddle with?
I have used this trick to add extra operations for turning off audit tracking before schema changes, I'm not sure what you would need to detect attribute changes.
Just an example of showing how to intercept with the migration SQL generation is good enough for me. I'll handle the rest from there. Thanks!
I just overrode MigrationsModelDiffer GetDifferences, examined each operation and if I found an operation that matched what I was looking for, I inserted additional operations into the returned collection. You might want to override the Diff method for comparing IProperty's, so you can add the operations you need.
0

The issue is that AddColumnOperation method is called only when a new column is added to the existing table.

For CreateTable you need to override void Generate(CreateTableOperation operation, IModel model, MigrationCommandListBuilder builder) method instead. CreateTableOperation contains Operations property of the same AddColumnOperation type.

Here is a full example

protected override void Generate(CreateTableOperation operation, IModel model, MigrationCommandListBuilder builder) { base.Generate(operation, model, builder); foreach (var columnOperation in operation.Columns) //columnOperation is AddColumnOperation { //operation.FindAnnotation("MyAttribute") } } 

Hope this helps!

1 Comment

Thank you. However this is not what I originally wanted. Similar to yours, I've found alternative solutions to workaround this issue. But I still want to know if there is a way to customize instructions for how migration code should be generated. So I'll leave this question as active now.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.