EF迁移代码优先.如果列没有';不存在
本文关键字:不存在 代码 迁移 如果 EF | 更新日期: 2023-09-27 18:27:33
如果表中不存在列,我需要添加该列。原因:在某些数据库中,我们有这样的列,而在某些数据库则没有。我在模型中添加了字段并创建了迁移:
public override void Up()
{
AddColumn("dbo.NavFilters", "Promo", c => c.String(maxLength: 100, nullable:true));
}
public override void Down()
{
DropColumn("dbo.NavFilters", "Promo");
}
如何检查该列是否存在?
我创建了一个自定义迁移方法AddColumnIfNotExists
您需要一个自定义的MigrationOperation类:
public class AddColumnIfNotExistsOperation : MigrationOperation
{
public readonly string Table;
public readonly string Name;
public readonly ColumnModel ColumnModel;
public AddColumnIfNotExistsOperation(string table, string name, Func<ColumnBuilder, ColumnModel> columnAction, object anonymousArguments) : base(anonymousArguments)
{
ArgumentValidator.CheckForEmptyArgument(table, nameof(table));
ArgumentValidator.CheckForEmptyArgument(name, nameof(name));
ArgumentValidator.CheckForNullArgument(columnAction, nameof(columnAction));
Table = table;
Name = name;
ColumnModel = columnAction(new ColumnBuilder());
ColumnModel.Name = name;
}
public override bool IsDestructiveChange => false;
public override MigrationOperation Inverse => new DropColumnOperation(Table, Name, removedAnnotations: ColumnModel.Annotations.ToDictionary(s => s.Key,s => (object)s.Value) , anonymousArguments: null);
}
您还需要一个自定义的SqlGenerator类:
public class AddColumnIfNotExistsSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(MigrationOperation migrationOperation)
{
var operation = migrationOperation as AddColumnIfNotExistsOperation;
if (operation == null) return;
using (var writer = Writer())
{
writer.WriteLine("IF NOT EXISTS(SELECT 1 FROM sys.columns");
writer.WriteLine($"WHERE Name = N'{operation.Name}' AND Object_ID = Object_ID(N'{Name(operation.Table)}'))");
writer.WriteLine("BEGIN");
writer.WriteLine("ALTER TABLE ");
writer.WriteLine(Name(operation.Table));
writer.Write(" ADD ");
var column = operation.ColumnModel;
Generate(column, writer);
if (column.IsNullable != null
&& !column.IsNullable.Value
&& (column.DefaultValue == null)
&& (string.IsNullOrWhiteSpace(column.DefaultValueSql))
&& !column.IsIdentity
&& !column.IsTimestamp
&& !column.StoreType.EqualsIgnoreCase("rowversion")
&& !column.StoreType.EqualsIgnoreCase("timestamp"))
{
writer.Write(" DEFAULT ");
if (column.Type == PrimitiveTypeKind.DateTime)
{
writer.Write(Generate(DateTime.Parse("1900-01-01 00:00:00", CultureInfo.InvariantCulture)));
}
else
{
writer.Write(Generate((dynamic)column.ClrDefaultValue));
}
}
writer.WriteLine("END");
Statement(writer);
}
}
}
以及一种扩展方法,为您提供"AddColumnIfNotExists"函数:
public static class MigrationExtensions
{
public static void AddColumnIfNotExists(this DbMigration migration, string table, string name, Func<ColumnBuilder, ColumnModel> columnAction, object anonymousArguments = null)
{
((IDbMigration)migration)
.AddOperation(new AddColumnIfNotExistsOperation(table, name, columnAction, anonymousArguments));
}
}
在EF迁移配置文件中,您需要注册自定义SQL生成器:
[ExcludeFromCodeCoverage]
internal sealed class Configuration : DbMigrationsConfiguration<YourDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
// Register our custom generator
SetSqlGenerator("System.Data.SqlClient", new AddColumnIfNotExistsSqlGenerator());
}
}
然后,您应该能够像这样使用它来代替AddColum(注意this关键字):
[ExcludeFromCodeCoverage]
public partial class AddVersionAndChangeActivity : DbMigration
{
public override void Up()
{
this.AddColumnIfNotExists("dbo.Action", "VersionId", c => c.Guid(nullable: false));
AlterColumn("dbo.Action", "Activity", c => c.String(nullable: false, maxLength: 8000, unicode: false));
}
public override void Down()
{
AlterColumn("dbo.Action", "Activity", c => c.String(nullable: false, maxLength: 50));
DropColumn("dbo.Action", "VersionId");
}
}
当然,你需要一些操作测试:
[TestClass]
public class AddColumnIfNotExistsOperationTests
{
[TestMethod]
public void Can_get_and_set_table_and_column_info()
{
Func<ColumnBuilder, ColumnModel> action = c => c.Decimal(name: "T");
var addColumnOperation = new AddColumnIfNotExistsOperation("T", "C", action, null);
Assert.AreEqual("T", addColumnOperation.Table);
Assert.AreEqual("C", addColumnOperation.Name);
}
[TestMethod]
public void Inverse_should_produce_drop_column_operation()
{
Func<ColumnBuilder, ColumnModel> action = c => c.Decimal(name: "C", annotations: new Dictionary<string, AnnotationValues> { { "A1", new AnnotationValues(null, "V1") } });
var addColumnOperation = new AddColumnIfNotExistsOperation("T", "C", action, null);
var dropColumnOperation = (DropColumnOperation)addColumnOperation.Inverse;
Assert.AreEqual("C", dropColumnOperation.Name);
Assert.AreEqual("T", dropColumnOperation.Table);
Assert.AreEqual("V1", ((AnnotationValues)dropColumnOperation.RemovedAnnotations["A1"]).NewValue);
Assert.IsNull(((AnnotationValues)dropColumnOperation.RemovedAnnotations["A1"]).OldValue);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Ctor_should_validate_preconditions_tableName()
{
Func<ColumnBuilder, ColumnModel> action = c => c.Decimal(name: "T");
// ReSharper disable once ObjectCreationAsStatement
new AddColumnIfNotExistsOperation(null, "T", action, null);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Ctor_should_validate_preconditions_columnName()
{
Func<ColumnBuilder, ColumnModel> action = c => c.Decimal();
// ReSharper disable once ObjectCreationAsStatement
new AddColumnIfNotExistsOperation("T", null, action, null);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Ctor_should_validate_preconditions_columnAction()
{
// ReSharper disable once ObjectCreationAsStatement
new AddColumnIfNotExistsOperation("T", "C", null, null);
}
}
SQL生成器的测试:
[TestClass]
public class AddColumnIfNotExistsSqlGeneratorTests
{
[TestMethod]
public void AddColumnIfNotExistsSqlGenerator_Generate_can_output_add_column_statement_for_GUID_and_uses_newid()
{
var migrationSqlGenerator = new AddColumnIfNotExistsSqlGenerator();
Func<ColumnBuilder, ColumnModel> action = c => c.Guid(nullable: false, identity: true, name: "Bar");
var addColumnOperation = new AddColumnIfNotExistsOperation("Foo", "bar", action, null);
var sql = string.Join(Environment.NewLine, migrationSqlGenerator.Generate(new[] {addColumnOperation}, "2005")
.Select(s => s.Sql));
Assert.IsTrue(sql.Contains("IF NOT EXISTS(SELECT 1 FROM sys.columns"));
Assert.IsTrue(sql.Contains("WHERE Name = N''bar'' AND Object_ID = Object_ID(N''[Foo]''))"));
Assert.IsTrue(sql.Contains("BEGIN"));
Assert.IsTrue(sql.Contains("ALTER TABLE"));
Assert.IsTrue(sql.Contains("[Foo]"));
Assert.IsTrue(sql.Contains("ADD [bar] [uniqueidentifier] NOT NULL DEFAULT newsequentialid()END"));
}
}
使用标准DbMigration方法无法实现
最好的方法是在try-catch中包含一个"select fieldToCheck from myTable where 1=2",然后根据需要添加字段(在catch中)。
另一种方法是编写一个自定义迁移生成器来扩展迁移生成器(即添加AddColumnIfNotExists方法)。您可以在此处查看如何操作:
http://romiller.com/2013/02/27/ef6-writing-your-own-code-first-migration-operations/
sql的基本示例:
// add colun if not exists
migrationBuilder.Sql(
@"IF COL_LENGTH('schemaName.TableName', 'ColumnName') IS NULL
ALTER TABLE[TableName] ADD[ColumnName] int NULL
GO
");