È possibile ignorare il MigrationSqlGenerator
che viene utilizzata da Entity Framework chiamando il metodo DbMigrationsConfiguration.SetSqlGenerator() nel costruttore della classe DbMigrationsConfiguration
, passando il nome del provider di database (ad esempio "System.Data.SqlClient"
per SQL Server), e l'istanza MigrationSqlGenerator
da utilizzare per il database fornitore.
Si consideri l'esempio da the work item che si è collegato a:
public class MyEntity
{
public int Id { get; set; }
[Required]
[MinLength(5)]
public string Name { get; set; }
}
Supponiamo che la tabella per MyEntity
era già stata prodotta e il comando Add-Migration
è stato utilizzato per aggiungere il campo Name
.
Per impostazione predefinita, la migrazione è ponteggi:
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");
}
}
Si noti che lo scaffolder non ha generato nulla per il MinLengthAttribute
.
Per far convocare EF il requisito di lunghezza minima, è possibile specify an attribute-to-column annotation convention. Come menzionato in quella pagina di documentazione, qualsiasi AnnotationValues
viene ignorato dai generatori SQL predefiniti.
All'interno della vostra di DbContext OnModelCreating() override, aggiungere il seguente:
modelBuilder.Conventions.Add(new AttributeToColumnAnnotationConvention<MinLengthAttribute, Int32>("minLength", (property, attributes) => attributes.Single().Length));
Dopo aver aggiunto che, è possibile rigenerare la migrazione ponteggi eseguendo Add-Migration -Force AddMyEntity_Name
. Ora la migrazione ponteggi è:
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" },
});
}
}
Supponiamo che, come in l'elemento di lavoro collegato, si desidera generare un vincolo per verificare che il valore di Name
rifilato è maggiore del minLength (5 in questo caso).
Si può iniziare con la creazione di un custom MigrationSqlGenerator
che si estende SqlServerMigrationSqlGenerator
e chiamare SetSqlGenerator() per installare il costume 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)
{
//...
}
}
In questo momento, questo CustomSqlServerMigrationSqlGenerator
l'override del metodo Generate (AddColumnOperation), ma semplicemente chiama il implementazione di base.
Se si guarda the documentation of AddColumnOperation
, verranno visualizzate due proprietà importanti, Column
e Table
. Column
è il ColumnModel
creato dal lambda in Up(), c => c.String(nullable: false, annotations: ...)
.
Nel metodo Genera(), è possibile accedere all'animazione AnnotationValues
tramite la proprietà Annotations
dello ColumnModel
.
Per generare il DDL che aggiunge il vincolo, è necessario generare l'SQL e chiamare il metodo Statement(). Per esempio:
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());
}
}
}
}
}
}
Se si esegue Update-Database -Verbose
, si vedrà un eccezione generata da CustomSqlServerMigrationSqlGenerator
:
minLength 5 specified for dbo.MyEntity.Name, but the default value, '', does not satisfy this requirement.
Per risolvere questo problema, specificare un defaultValue nella Up() che è più lungo del lunghezza minima (ad es "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")
},
}));
}
Ora, se si ri-run Update-Database -Verbose
, si vedrà la ALTER TABLE
dichiarazione che aggiunge la colonna e la dichiarazione ALTER TABLE
che aggiunge il vincolo:
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)
Consulta anche: EF6: Writing Your Own Code First Migration Operations, che mostra come implementare un'operazione di migrazione personalizzata.