我已将[Required]
数据注释添加到ASP.NET MVC application中的一个模型中。创建迁移后,运行Update-Database
命令会导致以下错误:
无法将值NULL插入列'Director',表'MOVIES_cf7bad808fa94f89afa2e5dae1161e78.dbo.Movies';列不允许空值。更新失败。该语句已终止。
这是因为一些记录在Director
列中有NULL。如何自动将这些值更改为默认值(例如“John Doe”)导演?
这是我的模型:
public class Movie
{
public int ID { get; set; }
[Required]
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
public string Genre { get; set; }
[Range(1,100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[StringLength(5)]
public string Rating { get; set; }
[Required] /// <--- NEW
public string Director { get; set; }
}
这是我最近的迁移:
public partial class AddDataAnnotationsMig : DbMigration
{
public override void Up()
{
AlterColumn("dbo.Movies", "Title", c => c.String(nullable: false));
AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false));
AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false));
}
public override void Down()
{
AlterColumn("dbo.Movies", "Director", c => c.String());
AlterColumn("dbo.Movies", "Rating", c => c.String());
AlterColumn("dbo.Movies", "Genre", c => c.String());
AlterColumn("dbo.Movies", "Title", c => c.String());
}
}
如果我没记错的话,这样的事情应该有效:
AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false, defaultValueSql: "John Doe"));
除了@webdeveloper和@Pushpendra的答案之外,您还需要手动向迁移添加更新以更新现有行。例如:
public override void Up()
{
Sql("UPDATE [dbo].[Movies] SET Title = 'No Title' WHERE Title IS NULL");
AlterColumn("dbo.Movies", "Title", c => c.String(nullable: false,defaultValue:"MyTitle"));
}
这是因为AlterColumn
生成DDL以将列的默认值设置为表规范中的某个特定值。 DDL不会影响数据库中的现有行。
你实际上是在同一时间进行两次更改(设置默认值并使列为NOT NULL),并且每个更改都是单独有效的,但由于你是同时制作两个,所以你可以期望系统'智能地'实现你的意图并将所有NULL
值设置为默认值,但这不是所期望的。
假设您只设置列的默认值,而不是使其为NOT NULL。您显然不希望使用您提供的默认值更新所有NULL记录。
因此,在我看来,这不是一个错误,我不希望EF以我没有明确告诉它的方式更新我的数据。开发人员负责指示系统如何处理数据。
public partial class AddDataAnnotationsMig : DbMigration
{
public override void Up()
{
AlterColumn("dbo.Movies", "Title", c => c.String(nullable: false,defaultValue:"MyTitle"));
AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false,defaultValue:"Genre"));
AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false,defaultValue:"Director"));
}
public override void Down()
{
AlterColumn("dbo.Movies", "Director", c => c.String());
AlterColumn("dbo.Movies", "Rating", c => c.String());
AlterColumn("dbo.Movies", "Genre", c => c.String());
AlterColumn("dbo.Movies", "Title", c => c.String());
}
}
不确定这个选项是否总是存在但只是遇到了类似的问题,发现我能够设置默认值而不使用以下运行任何手动更新
defaultValueSql: "'NY'"
当提供的值是"NY"
时我得到了一个错误然后我意识到他们期待像"GETDATE()"
这样的SQL值所以我尝试了"'NY'"
并且这样做了
整条线看起来像这样
AddColumn("TABLE_NAME", "State", c => c.String(maxLength: 2, nullable: false, defaultValueSql: "'NY'"));
感谢this answer,让我走上正轨
我发现只在实体属性上使用Auto-Property Initializer就足以完成工作。
例如:
public class Thing {
public bool IsBigThing { get; set; } = false;
}
出于某种原因,我无法解释自己批准的答案对我来说不再适用。
它适用于另一个应用程序,在我工作的应用程序上没有。
因此,另一种但非常低效的解决方案是覆盖SaveChanges()方法,如下所示。此方法应该在Context类上。
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries().Where(entry => entry.Entity.GetType().GetProperty("ColumnName") != null))
{
if (entry.State == EntityState.Added)
{
entry.Property("ColumnName").CurrentValue = "DefaultValue";
}
}
许多其他响应都集中在如何在发生这些问题时手动干预。
生成迁移后,请对迁移执行以下任一更改:
- 修改Column定义以包含defaultValue或defaultSql语句:
AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false, default: ""));
- 在AlterColumn之前注入一个SQL语句以预填充现有列:
Sql("UPDATE dbo.Movies SET Director = '' WHERE Director IS NULL");
请记住,如果重新构建迁移,将覆盖应用于迁移脚本的手动更改。对于第一个解决方案,扩展EF非常容易,可以在迁移生成过程中自动定义字段的默认值。
注意:EF不会自动为您执行此操作,因为每个RDBMS提供程序的默认值实现会有所不同,但也因为默认值在纯EF运行时中的含义较少,因为每个行插入将为每个属性提供当前值,即使它为null,也永远不会计算默认值约束。 这个AlterColumn语句是默认约束发挥作用的唯一时间,我想这对于设计SQL Server迁移实现的团队来说是一个较低的优先级。
以下解决方案结合了属性表示法,模型配置约定和列注释,以将元数据传递给自定义迁移代码生成器。如果您不使用属性表示法,则可以使用每个受影响字段的流利表示法替换步骤1和2。 这里有很多技巧,随意使用部分或全部,我希望这里的每个人都有价值
[DefaultValue("Insert DefaultValue Here")]
[Required] /// <--- NEW
public string Director { get; set; }
// Example of default value sql
[DefaultValue(DefaultValueSql: "GetDate()")]
[Required]
public string LastModified { get; set; }
Attrribute定义
namespace EFExtensions
{
/// <summary>
/// Specifies the default value for a property but allows a custom SQL statement to be provided as well. <see cref="MiniTuber.Database.Conventions.DefaultValueConvention"/>
/// </summary>
public class DefaultValueAttribute : System.ComponentModel.DefaultValueAttribute
{
/// <summary>
/// Specifies the default value for a property but allows a custom SQL statement to be provided as well. <see cref="MiniTuber.Database.Conventions.DefaultValueConvention"/>
/// </summary>
public DefaultValueAttribute() : base("")
{
}
/// <i
/// <summary>
/// Optional SQL to use to specify the default value.
/// </summary>
public string DefaultSql { get; set; }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class using a Unicode character.
/// </summary>
/// <param name="value">
/// A Unicode character that is the default value.
/// </param>
public DefaultValueAttribute(char value) : base(value) { }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class using an 8-bit unsigned integer.
/// </summary>
/// <param name="value">
/// An 8-bit unsigned integer that is the default value.
/// </param>
public DefaultValueAttribute(byte value) : base(value) { }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class using a 16-bit signed integer.
/// </summary>
/// <param name="value">
/// A 16-bit signed integer that is the default value.
/// </param>
public DefaultValueAttribute(short value) : base(value) { }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class using a 32-bit signed integer.
/// </summary>
/// <param name="value">
/// A 32-bit signed integer that is the default value.
/// </param>
public DefaultValueAttribute(int value) : base(value) { }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class using a 64-bit signed integer.
/// </summary>
/// <param name="value">
/// A 64-bit signed integer that is the default value.
/// </param>
public DefaultValueAttribute(long value) : base(value) { }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class using a single-precision floating point number.
/// </summary>
/// <param name="value">
/// A single-precision floating point number that is the default value.
/// </param>
public DefaultValueAttribute(float value) : base(value) { }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class using a double-precision floating point number.
/// </summary>
/// <param name="value">
/// A double-precision floating point number that is the default value.
/// </param>
public DefaultValueAttribute(double value) : base(value) { }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class using a System.Boolean value.
/// </summary>
/// <param name="value">
/// A System.Boolean that is the default value.
/// </param>
public DefaultValueAttribute(bool value) : base(value) { }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class using a System.String.
/// </summary>
/// <param name="value">
/// A System.String that is the default value.
/// </param>
public DefaultValueAttribute(string value) : base(value) { }
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class.
/// </summary>
/// <param name="value">
/// An System.Object that represents the default value.
/// </param>
public DefaultValueAttribute(object value) : base(value) { }
/// /// <inheritdoc/>
/// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
/// class, converting the specified value to the specified type, and using an invariant
/// culture as the translation context.
/// </summary>
/// <param name="type">
/// A System.Type that represents the type to convert the value to.
/// </param>
/// <param name="value">
/// A System.String that can be converted to the type using the System.ComponentModel.TypeConverter
/// for the type and the U.S. English culture.
/// </param>
public DefaultValueAttribute(Type type, string value) : base(value) { }
}
}
namespace EFExtensions
{
/// <summary>
/// Implement SQL Default Values from System.ComponentModel.DefaultValueAttribute
/// </summary>
public class DefaultValueConvention : Convention
{
/// <summary>
/// Annotation Key to use for Default Values specified directly as an object
/// </summary>
public const string DirectValueAnnotationKey = "DefaultValue";
/// <summary>
/// Annotation Key to use for Default Values specified as SQL Strings
/// </summary>
public const string SqlValueAnnotationKey = "DefaultSql";
/// <summary>
/// Implement SQL Default Values from System.ComponentModel.DefaultValueAttribute
/// </summary>
public DefaultValueConvention()
{
// Implement SO Default Value Attributes first
this.Properties()
.Where(x => x.HasAttribute<EFExtensions.DefaultValueAttribute>())
.Configure(c => c.HasColumnAnnotation(
c.GetAttribute<EFExtensions.DefaultValueAttribute>().GetDefaultValueAttributeKey(),
c.GetAttribute<EFExtensions.DefaultValueAttribute>().GetDefaultValueAttributeValue()
));
// Implement Component Model Default Value Attributes, but only if it is not the SO implementation
this.Properties()
.Where(x => x.HasAttribute<System.ComponentModel.DefaultValueAttribute>())
.Where(x => !x.HasAttribute<MiniTuber.DataAnnotations.DefaultValueAttribute>())
.Configure(c => c.HasColumnAnnotation(
DefaultValueConvention.DirectValueAnnotationKey,
c.GetAttribute<System.ComponentModel.DefaultValueAttribute>().Value
));
}
}
/// <summary>
/// Extension Methods to simplify the logic for building column annotations for Default Value processing
/// </summary>
public static partial class PropertyInfoAttributeExtensions
{
/// <summary>
/// Wrapper to simplify the lookup for a specific attribute on a property info.
/// </summary>
/// <typeparam name="T">Type of attribute to lookup</typeparam>
/// <param name="self">PropertyInfo to inspect</param>
/// <returns>True if an attribute of the requested type exists</returns>
public static bool HasAttribute<T>(this PropertyInfo self) where T : Attribute
{
return self.GetCustomAttributes(false).OfType<T>().Any();
}
/// <summary>
/// Wrapper to return the first attribute of the specified type
/// </summary>
/// <typeparam name="T">Type of attribute to return</typeparam>
/// <param name="self">PropertyInfo to inspect</param>
/// <returns>First attribuite that matches the requested type</returns>
public static T GetAttribute<T>(this System.Data.Entity.ModelConfiguration.Configuration.ConventionPrimitivePropertyConfiguration self) where T : Attribute
{
return self.ClrPropertyInfo.GetCustomAttributes(false).OfType<T>().First();
}
/// <summary>
/// Helper to select the correct DefaultValue annotation key based on the attribute values
/// </summary>
/// <param name="self"></param>
/// <returns></returns>
public static string GetDefaultValueAttributeKey(this EFExtensions.DefaultValueAttribute self)
{
return String.IsNullOrWhiteSpace(self.DefaultSql) ? DefaultValueConvention.DirectValueAnnotationKey : DefaultValueConvention.SqlValueAnnotationKey;
}
/// <summary>
/// Helper to select the correct attribute property to send as a DefaultValue annotation value
/// </summary>
/// <param name="self"></param>
/// <returns></returns>
public static object GetDefaultValueAttributeValue(this EFExtensions.DefaultValueAttribute self)
{
return String.IsNullOrWhiteSpace(self.DefaultSql) ? self.Value : self.DefaultSql;
}
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Use our new DefaultValueConvention
modelBuilder.Conventions.Add<EFExtensions.DefaultValueConvention>();
// My personal favourites ;)
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
System.Data.Entity.Migrations.Design.CSharpMigrationCodeGenerator
,因为我们只需要注入最少量的更改。
处理完我们的自定义注释后,我们需要将其从列定义中删除,以防止它被序列化为最终输出。
请参阅基类代码以探索其他用法:http://entityframework.codeplex.com/sourcecontrol/latest#src/EntityFramework/Migrations/Design/CSharpMigrationCodeGenerator.cs
namespace EFExtensions
{
/// <summary>
/// Implement DefaultValue constraint definition in Migration Scripts.
/// </summary>
/// <remarks>
/// Original guide that provided inspiration for this https://romiller.com/2012/11/30/code-first-migrations-customizing-scaffolded-code/
/// </remarks>
public class CustomCodeGenerator : System.Data.Entity.Migrations.Design.CSharpMigrationCodeGenerator
{
/// <summary>
/// Inject Default values from the DefaultValue attribute, if the DefaultValueConvention has been enabled.
/// </summary>
/// <seealso cref="DefaultValueConvention"/>
/// <param name="column"></param>
/// <param name="writer"></param>
/// <param name="emitName"></param>
protected override void Generate(ColumnModel column, IndentedTextWriter writer, bool emitName = false)
{
var annotations = column.Annotations?.ToList();
if (annotations != null && annotations.Any())
{
for (int index = 0; index < annotations.Count; index ++)
{
var annotation = annotations[index];
bool handled = true;
try
{
switch (annotation.Key)
{
case DefaultValueConvention.SqlValueAnnotationKey:
if (annotation.Value?.NewValue != null)
{
column.DefaultValueSql = $"{annotation.Value.NewValue}";
}
break;
case DefaultValueConvention.DirectValueAnnotationKey:
if (annotation.Value?.NewValue != null)
{
column.DefaultValue = Convert.ChangeType(annotation.Value.NewValue, column.ClrType);
}
break;
default:
handled = false;
break;
}
}
catch(Exception ex)
{
// re-throw with specific debug information
throw new ApplicationException($"Failed to Implement Column Annotation for column: {column.Name} with key: {annotation.Key} and new value: {annotation.Value.NewValue}", ex);
}
if(handled)
{
// remove the annotation, it has been applied
column.Annotations.Remove(annotation.Key);
}
}
}
base.Generate(column, writer, emitName);
}
/// <summary>
/// Generates class summary comments and default attributes
/// </summary>
/// <param name="writer"> Text writer to add the generated code to. </param>
/// <param name="designer"> A value indicating if this class is being generated for a code-behind file. </param>
protected override void WriteClassAttributes(IndentedTextWriter writer, bool designer)
{
writer.WriteLine("/// <summary>");
writer.WriteLine("/// Definition of the Migration: {0}", this.ClassName);
writer.WriteLine("/// </summary>");
writer.WriteLine("/// <remarks>");
writer.WriteLine("/// Generated Time: {0}", DateTime.Now);
writer.WriteLine("/// Generated By: {0}", Environment.UserName);
writer.WriteLine("/// </remarks>");
base.WriteClassAttributes(writer, designer);
}
}
}
internal sealed class Configuration : DbMigrationsConfiguration<YourApplication.Database.Context>
{
public Configuration()
{
// I recommend that auto-migrations be disabled so that we control
// the migrations explicitly
AutomaticMigrationsEnabled = false;
CodeGenerator = new EFExtensions.CustomCodeGenerator();
}
protected override void Seed(YourApplication.Database.Context context)
{
// Your custom seed logic here
}
}
从EF Core 2.1开始,您可以在更改列之前使用MigrationBuilder.UpdateData
更改值(比使用原始SQL更干净):
protected override void Up(MigrationBuilder migrationBuilder)
{
// Change existing NULL values to NOT NULL values
migrationBuilder.UpdateData(
table: tableName,
column: columnName,
value: valueInsteadOfNull,
keyColumn: columnName,
keyValue: null);
// Change column type to NOT NULL
migrationBuilder.AlterColumn<ColumnType>(
table: tableName,
name: columnName,
nullable: false,
oldClrType: typeof(ColumnType),
oldNullable: true);
}