Entity Framework 6: How to override SQL generator?

I would like to change the SQL generated by EF: CF when creating the database schema (DDL) as suggested by the Entity Framework team .

How can I do that?

I could not find anything suitable through Google.

+7
entity-framework ef-code-first code-first-migrations entity-framework-6
source share
1 answer

You can override the MigrationSqlGenerator used by the Entity Framework by calling DbMigrationsConfiguration.SetSqlGenerator () in the constructor of your DbMigrationsConfiguration class, passing the name of the database provider (for example, "System.Data.SqlClient" for SQL Server) and the instance of MigrationSqlGenerator that will be used to do this database provider.

Consider the example from the work item that you contacted:

 public class MyEntity { public int Id { get; set; } [Required] [MinLength(5)] public string Name { get; set; } } 

Suppose a table for MyEntity has already been generated, and the Add-Migration command has been used to add the Name field.

Default Migration of Lining:

 public partial class AddMyEntity_Name : DbMigration { public override void Up() { AddColumn("dbo.MyEntity", "Name", c => c.String(nullable: false)); } public override void Down() { DropColumn("dbo.MyEntity", "Name"); } } 

Note that scaffolder did not generate anything for MinLengthAttribute .

For EF to pass a minimum length requirement, you can specify an agreement to annotate an attribute on a column . As mentioned on this documentation page, any AnnotationValues ignored by default SQL generators.

Inside the DbContext OnModelCreating () override, add the following:

 modelBuilder.Conventions.Add(new AttributeToColumnAnnotationConvention<MinLengthAttribute, Int32>("minLength", (property, attributes) => attributes.Single().Length)); 

After adding this parameter, you can restore the forest migration by executing Add-Migration -Force AddMyEntity_Name . Now forest migration:

 public partial class AddMyEntity_Name : DbMigration { public override void Up() { AddColumn("dbo.MyEntity", "Name", c => c.String(nullable: false, annotations: new Dictionary<string, AnnotationValues> { { "minLength", new AnnotationValues(oldValue: null, newValue: "5") }, })); } public override void Down() { DropColumn("dbo.MyEntity", "Name", removedAnnotations: new Dictionary<string, object> { { "minLength", "5" }, }); } } 

Suppose that, as in the linked work item, you want to generate a constraint to verify that the cropped value of Name greater than minLength (in this case 5).

You can start by creating a custom MigrationSqlGenerator that extends SqlServerMigrationSqlGenerator and calls SetSqlGenerator () to set a custom MigrationSqlGenerator :

 internal class CustomSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator { protected override void Generate(AddColumnOperation addColumnOperation) { base.Generate(addColumnOperation); } } internal sealed class Configuration : DbMigrationsConfiguration<DataContext> { public Configuration() { AutomaticMigrationsEnabled = false; SetSqlGenerator("System.Data.SqlClient", new CustomSqlServerMigrationSqlGenerator()); } protected override void Seed(DataContext context) { //... } } 

Currently, this CustomSqlServerMigrationSqlGenerator overrides the Generate (AddColumnOperation) method, but just calls the underlying implementation.

If you look at the AddColumnOperation documentation , you will see two important properties: Column and Table . Column is the ColumnModel that was created by the lambda in Up (), c => c.String(nullable: false, annotations: ...) .

In the Generate () method, you can access the custom AnnotationValues using the Annotations ColumnModel .

To generate a DDL that adds a constraint, you need to generate SQL and call the Statement () method. For example:

 internal class CustomSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator { protected override void Generate(AddColumnOperation addColumnOperation) { base.Generate(addColumnOperation); var column = addColumnOperation.Column; if (column.Type == System.Data.Entity.Core.Metadata.Edm.PrimitiveTypeKind.String) { var annotations = column.Annotations; AnnotationValues minLengthValues; if (annotations.TryGetValue("minLength", out minLengthValues)) { var minLength = Convert.ToInt32(minLengthValues.NewValue); if (minLength > 0) { if (Convert.ToString(column.DefaultValue).Trim().Length < minLength) { throw new ArgumentException(String.Format("minLength {0} specified for {1}.{2}, but the default value, '{3}', does not satisfy this requirement.", minLength, addColumnOperation.Table, column.Name, column.DefaultValue)); } using (var writer = new StringWriter()) { writer.Write("ALTER TABLE "); writer.Write(Name(addColumnOperation.Table)); writer.Write(" ADD CONSTRAINT "); writer.Write(Quote("ML_" + addColumnOperation.Table + "_" + column.Name)); writer.Write(" CHECK (LEN(LTRIM(RTRIM({0}))) > {1})", Quote(column.Name), minLength); Statement(writer.ToString()); } } } } } } 

If you run Update-Database -Verbose , you will see an exception thrown using CustomSqlServerMigrationSqlGenerator :

  minLength 5 specified for dbo.MyEntity.Name, but the default value, '', does not satisfy this requirement.

To fix this problem, specify a default value in the Up () method that is greater than the minimum length (for example, "unknown" ):

  public override void Up() { AddColumn("dbo.MyEntity", "Name", c => c.String(nullable: false, defaultValue: "unknown", annotations: new Dictionary<string, AnnotationValues> { { "minLength", new AnnotationValues(oldValue: null, newValue: "5") }, })); } 

Now, if you run Update-Database -Verbose , you will see an ALTER TABLE statement that adds a column and an ALTER TABLE statement that adds a constraint:

  ALTER TABLE [dbo]. [MyEntity] ADD [Name] [nvarchar] (max) NOT NULL DEFAULT 'unknown'
 ALTER TABLE [dbo]. [MyEntity] ADD CONSTRAINT [ML_dbo.MyEntity_Name] CHECK (LEN (LTRIM (RTRIM ([Name]))))> 5)

See also: EF6: Writing custom wrapping operations of the first code that shows how to implement a custom wrapping operation.

+15
source share

All Articles