Search code examples
entity-frameworkentity-framework-6ef-code-firstdefault-valueentity-framework-migrations

Entity Framework 6 Code first Default value overriding the MigrationCodeGenerator


All the approaches I've found about declaring default values, generates the default value in the Sql script, not in the migration code.

My favorite is using attributes: https://stackoverflow.com/a/34894274/132942

[SqlDefaultValue(DefaultValue = "getutcdate()")]
public DateTime CreatedDateUtc { get; set; }

Attribute definition

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class SqlDefaultValueAttribute : Attribute
{
    public string DefaultValue { get; set; }
}

In the "OnModelCreating" of the context adding

modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue));

And then customizing the SqlGenerator. But I don't like this because I don't see when generating the migration if everything it's as I wanted.

To do so, I modified the MigrationCodeGenerator like this: https://stackoverflow.com/a/21024108

In the custom MigrationCodeGenerator

public class ExtendedMigrationCodeGenerator : MigrationCodeGenerator
{
    public override ScaffoldedMigration Generate(string migrationId, IEnumerable<MigrationOperation> operations, string sourceModel, string targetModel, string @namespace, string className)
    {
        foreach (MigrationOperation operation in operations)
        {
            if (operation is CreateTableOperation)
            {
                foreach (var column in ((CreateTableOperation)operation).Columns)
                {
                    System.Data.Entity.Infrastructure.Annotations.AnnotationValues values;
                    if (column.Annotations.TryGetValue("SqlDefaultValue", out values))
                    {
                        column.DefaultValueSql = (string)values.NewValue;
                    }
                }
            }
            else if (operation is AddColumnOperation)
            {
                ColumnModel column = ((AddColumnOperation)operation).Column;

                System.Data.Entity.Infrastructure.Annotations.AnnotationValues values;
                if (column.Annotations.TryGetValue("SqlDefaultValue", out values))
                {
                    column.DefaultValueSql = (string)values.NewValue;
                }

            }
        }

        CSharpMigrationCodeGenerator generator = new CSharpMigrationCodeGenerator();

        return generator.Generate(migrationId, operations, sourceModel, targetModel, @namespace, className);
    }
}

But in the method ScaffoldedMigration I cannot get my custom annotation SqlDefaultValue or any other annotation.

Is it possible to get this annotation?


Solution

  • You haven't indicated how you registered your ExtendedMigrationCodeGenerator to be used, you can do this in the constructor of the Configuration class in Configuration.cs for example:

    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
        AutomaticMigrationDataLossAllowed = false;
        // Register the Customized Migration Generator to use
        CodeGenerator = new ExtendedMigrationCodeGenerator();
    }
    

    But also do not forget the AlterColumnOperation which might be your biggest issue if you are applying this to an existing schema.

    else if (operation is AlterColumnOperation alterColumnOp)
    {
        ColumnModel column = alterColumnOp.Column;
    
        System.Data.Entity.Infrastructure.Annotations.AnnotationValues values;
        if (column.Annotations.TryGetValue("SqlDefaultValue", out values))
        {
            column.DefaultValueSql = (string)values.NewValue;
        }
    }
    

    The other scenario where you will not see a generated output is if the convention and annotations were already applied in a previous migration that was generated before you configured the custom ExtendedMigrationCodeGenerator.

    Debugging tip:

    Debugging custom migration logic isn't as simple as setting a breakpoint, because it is normally executed by external processes like Migration.exe. So before breakpoints will work, we need to invoke the debugger, we can do this by inserting the following code either at the point where you want to debug or in the constructor of the migration code generator class:

    if (!System.Diagnostics.Debugger.IsAttached)
        System.Diagnostics.Debugger.Launch();
    

    It is better to attach in a constructor rather than near the code you want to debug because we know the constructor should be executed under normal conditions, but the reason your code isn't working might be due to the method or code branch not being executed at all, if it's not being executed, then the Launch() command also will not be executed.

    If you use this method to debug the migration and you don't get the debug attach dialog then either there are no migrations detected, or your ExtendedMigrationCodeGenerator hasn't been registered correctly.