ChatGPT解决这个技术问题 Extra ChatGPT

实体框架迁移中必填字段的默认值?

我已将 [Required] 数据注释添加到 ASP.NET MVC application 中的一个模型中。创建迁移后,运行 Update-Database 命令会导致以下错误:

无法将值 NULL 插入列“导演”,表“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());
    }
}

I
Iravanchi

除了来自@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 以我没有明确告诉它这样做的方式更新我的数据。开发人员负责指示系统如何处理数据。


对于通过谷歌找到这个答案的人:我刚刚在 EF6 中尝试过,更新语句似乎没有必要(不再)。我想他们毕竟认为这是一个错误。
我也可以担保。如果即使对于可为空的字段也需要默认值,只需先使用默认值将其更改为不可为空,然后再将其改回可为空。当您向子类添加不可为空的字段时非常方便:)
现场解释。 AlterColumn() 只是改变列定义。它不会影响现有记录
P
Phil

如果我没记错的话,这样的事情应该可以工作:

AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false, defaultValueSql: "'John Doe'"));

注意:defaultValueSql 参数值被视为逐字 SQL 语句,因此如果所需的值实际上是一个字符串,如 John Doe 示例,则需要在值周围加上单引号。


我也这么认为,但这似乎不适用于现有记录。所以我仍然得到一个错误。
@drozzy 也许它是错误,就像这里:EF 4.3.1 Migration Exception - AlterColumn defaultValueSql creates same default constraint name for different tables 您可以通过您的查询检查 IS NULL 更新行。
有趣,但我不确定我是否理解他们在说什么。但是,如果这是一个错误,那么是的,这是有道理的。
我认为应该是:"'John Doe'" - 您需要使用 SQL 引号。
@webdeveloper,我不认为这是一个错误,为什么 AlterColumn 会更新当前值?它是一个 DDL(不是 DML)命令。
P
Pushpendra
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());       
    }
}

嗯...谢谢,但这与@webdeveloper 的回答有何不同?
它没有告诉您必须在哪里添加默认值参数
@Pushpendra,有趣的是,开发人员往往会忘记从前他们并不了解太多。我喜欢满足所有级别的详细答案。很棒的工作!
C
Community

不确定此选项是否始终存在,但遇到了类似的问题,发现我能够设置默认值,而无需使用以下命令运行任何手动更新

defaultValueSql: "'NY'"

当提供的值是 "NY" 时出现错误,然后我意识到他们期待像 "GETDATE()" 这样的 SQL 值,所以我尝试了 "'NY'" 并且成功了

整条线看起来像这样

AddColumn("TABLE_NAME", "State", c => c.String(maxLength: 2, nullable: false, defaultValueSql: "'NY'"));

感谢this answer,让我走上了正轨


A
Antoine Robin

从 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);
}

C
Chris Schaller

许多其他响应集中在如何在这些问题发生时进行手动干预。

生成迁移后,对迁移执行以下任一更改: 修改列定义以包含 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 运行时中的意义较小,因为每个行插入都会为每个属性提供当前值,即使它是空的,所以默认值约束永远不会被评估。这个 AlterColumn 语句是唯一一次默认约束发挥作用,我想这对于设计 SQL Server 迁移实现的团队来说是一个较低的优先级。

以下解决方案结合了属性表示法、模型配置约定和列注释,以将元数据传递到自定义迁移代码生成器。如果您不使用属性表示法,则可以将步骤 1 和 2 替换为每个受影响字段的流畅表示法。这里有很多技巧在玩,随意使用部分或全部,希望对这里的每个人都有价值

声明默认值 创建或重新定义现有属性以定义要使用的默认值,在本例中,我们将创建一个名为 DefaultValue 的新属性,该属性继承自 ComponentModel.DefaultValueAttribute,因为它的用法很直观,并且有可能存在代码库已经实现了这个属性。使用此实现,您只需要使用此特定属性来访问 DefaultValueSql,这对于日期和其他自定义场景很有用。实现 [DefaultValue("Insert DefaultValue Here")] [Required] /// <--- NEW public string Director { get;放; } // 默认值示例 sql [DefaultValue(DefaultValueSql: "GetDate()")] [Required] public string LastModified { get;放; } 属性定义命名空间 EFExtensions { ///

/// 指定属性的默认值,但也允许提供自定义 SQL 语句。 /// public class DefaultValueAttribute : System.ComponentModel.DefaultValueAttribute { /// /// 指定属性的默认值,但允许还要提供一个自定义 SQL 语句。 /// public DefaultValueAttribute() : base("") { } /// /// 可选 SQL用于指定默认值。 /// public string DefaultSql { get;放; } /// /// 使用 Unicode 字符初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 类。 /// /// /// 作为默认值的 Unicode 字符。 /// public DefaultValueAttribute(char value) : base(value) { } /// /// 使用 8 位无符号初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 类整数。 /// /// /// 一个 8 位无符号整数,它是默认值。 /// public DefaultValueAttribute(byte value) : base(value) { } /// /// 初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 使用 16 位有符号的类整数。 /// /// /// 一个 16 位有符号整数,它是默认值。 /// public DefaultValueAttribute(short value) : base(value) { } /// /// 初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 使用 32 位有符号的类整数。 /// /// /// 一个 32 位有符号整数,它是默认值。 /// public DefaultValueAttribute(int value) : base(value) { } /// /// 初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 使用 64 位有符号的类整数。 /// /// /// 一个 64 位有符号整数,它是默认值。 /// public DefaultValueAttribute(long value) : base(value) { } /// /// 初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 使用单精度浮点数点数。 /// /// /// 作为默认值的单精度浮点数。 /// public DefaultValueAttribute(float value) : base(value) { } /// /// 使用双精度浮点初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 类点数。 /// /// /// 作为默认值的双精度浮点数。 /// public DefaultValueAttribute(double value) : base(value) { } /// /// 使用 System.Boolean 值初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 类. /// /// /// 作为默认值的 System.Boolean。 /// public DefaultValueAttribute(bool value) : base(value) { } /// /// 使用 System.String 初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 类。 /// /// /// 作为默认值的 System.String。 /// public DefaultValueAttribute(string value) : base(value) { } /// /// 初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 类。 /// /// /// 表示默认值的 System.Object。 /// public DefaultValueAttribute(object value) : base(value) { } /// /// /// 初始化 System.ComponentModel.DefaultValueAttribute 的新实例 /// 类,转换指定类型的指定值,并使用不变的 /// 文化作为翻译上下文。 /// /// /// System.Type 表示要将值转换为的类型。 /// /// /// 可以使用 System.ComponentModel.TypeConverter 转换为类型的 System.String /// 用于类型和美国英语文化. /// public DefaultValueAttribute(Type type, string value) : base(value) { } } } 创建一个约定,将默认值注入到列注解中 列注解用于传递关于列的自定义元数据到迁移脚本生成器。使用约定来执行此操作展示了属性表示法背后的强大功能,可以简化如何为许多属性定义和操作流畅的元数据,而不是为每个字段单独指定它。 namespace EFExtensions { /// /// 从 System.ComponentModel.DefaultValueAttribute 实现 SQL 默认值 /// public class DefaultValueConvention : Convention { /// /// 用于默认的注释键直接指定为对象的值 /// public const string DirectValueAnnotationKey = "DefaultValue"; /// /// 用于指定为 SQL 字符串的默认值的注释键 /// public const string SqlValueAnnotationKey = "DefaultSql"; /// /// 从 System.ComponentModel.DefaultValueAttribute 实现 SQL 默认值 /// public DefaultValueConvention() { // 首先实现 SO 默认值属性 this.Properties() 。其中(x => x.HasAttribute()) 。 Configure(c => c.HasColumnAnnotation( c.GetAttribute().GetDefaultValueAttributeKey(), c.GetAttribute().GetDefaultValueAttributeValue() )); // 实现组件模型默认值属性,但前提是它不是 SO 实现 this.Properties() 。其中(x => x.HasAttribute()) 。 Where(x => ! x.HasAttribute()) 。 Configure(c => c.HasColumnAnnotation( DefaultValueConvention.DirectValueAnnotationKey, c.GetAttribute().Value )); } } /// /// 用于简化为默认值处理构建列注释的逻辑的扩展方法 /// public static partial class PropertyInfoAttributeExtensions { /// /// 用于简化的包装器在属性信息上查找特定属性。 /// /// 要查找的属性类型 /// 要检查的PropertyInfo /// 如果请求类型的属性存在则为真 public static bool HasAttribute(this PropertyInfo self) where T : Attribute { return self.GetCustomAttributes(false)。类型()。任何(); } /// /// 包装器返回指定类型的第一个属性 /// /// 返回的属性类型 /// PropertyInfo to inspect /// 第一个匹配请求类型的属性 public static T GetAttribute(this System.Data.Entity.ModelConfiguration.Configuration .ConventionPrimitivePropertyConfiguration self) where T : Attribute { return self.ClrPropertyInfo.GetCustomAttributes(false)。类型()。第一的(); } /// /// Helper 根据属性值选择正确的 DefaultValue 注释键 /// /// /// public static string GetDefaultValueAttributeKey(this EFExtensions.DefaultValueAttribute self) { return String.IsNullOrWhiteSpace(self.DefaultSql) ? DefaultValueConvention.DirectValueAnnotationKey : DefaultValueConvention.SqlValueAnnotationKey; } /// /// 帮助器选择正确的属性属性作为 DefaultValue 注释值发送 /// /// /// <返回> public static object GetDefaultValueAttributeValue(this EFExtensions.DefaultValueAttribute self) { return String.IsNullOrWhiteSpace(self.DefaultSql) ? self.Value : self.DefaultSql; } } } 将约定添加到 DbContext 有很多方法可以实现这一点,我喜欢将约定声明为我的 ModelCreation 逻辑中的第一个自定义步骤,这将在您的 DbContext 类中。受保护的覆盖无效 OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 使用我们新的 DefaultValueConvention modelBuilder.Conventions.Add(); // 我个人的最爱 ;) modelBuilder.Conventions.Remove(); modelBuilder.Conventions.Remove();覆盖 MigrationCodeGenerator 现在这些注释已应用于模型中的列定义,我们需要修改迁移脚本生成器以使用这些注释。为此,我们将从 System.Data.Entity.Migrations.Design.CSharpMigrationCodeGenerator 继承,因为我们只需要注入最少量的更改。一旦我们处理了我们的自定义注解,我们需要将它从列定义中移除,以防止它被序列化到最终输出。查看基类代码以探索其他用法:http://entityframework.codeplex.com/sourcecontrol/latest#src/EntityFramework/Migrations/Design/CSharpMigrationCodeGenerator.cs namespace EFExtensions { /// /// 实现 DefaultValue 约束迁移脚本中的定义。 /// /// /// 为本文提供灵感的原始指南 https://romiller.com/2012/11/30/code-first-migrations-customizing-scaffolded-code/ / // public class CustomCodeGenerator : System.Data.Entity.Migrations.Design.CSharpMigrationCodeGenerator { /// /// 如果 DefaultValueConvention 已启用,则从 DefaultValue 属性注入默认值。 /// /// /// /// / // protected override void Generate(ColumnModel column, IndentedTextWriter writer, bool emitName = false) { var annotations = column.Annotations?.列表(); if (annotations ! = null && annotations.Any()) { for (int index = 0; index < annotations.Count; index ++) { var annotation = annotations[index];布尔处理=真;尝试 { switch (annotation.Key) { case DefaultValueConvention.SqlValueAnnotationKey: if (annotation.Value?.NewValue ! = null) { column.DefaultValueSql = $"{annotation.Value.NewValue}"; } 休息; case DefaultValueConvention.DirectValueAnnotationKey: if (annotation.Value?.NewValue != null) { column.DefaultValue = Convert.ChangeType(annotation.Value.NewValue, column.ClrType); } 休息;默认值:处理 = 假;休息; } } catch(Exception ex) { // 使用特定调试信息重新抛出 throw new ApplicationException($"Failed to Implement Column Annotation for column: {column.Name} with key: {annotation.Key} and new value: {annotation .Value.NewValue}”,例如); } if(handled) { // 移除注解,它已被应用 column.Annotations.Remove(annotation.Key); } } } base.Generate(column, writer, emitName); } /// /// 生成类摘要注释和默认属性 /// /// 将生成的代码添加到的文本编写器。 /// 一个值,指示是否正在为代码隐藏文件生成此类。 protected override void WriteClassAttributes(IndentedTextWriter writer, bool design) { writer.WriteLine("/// "); writer.WriteLine("/// 迁移定义:{0}", this.ClassName); writer.WriteLine("/// "); writer.WriteLine("/// <备注>"); writer.WriteLine("/// 生成时间:{0}", DateTime.Now); writer.WriteLine("/// 生成者:{0}", Environment.UserName); writer.WriteLine("/// "); base.WriteClassAttributes(作家,设计师); } } } 注册 CustomCodeGenerator 最后一步,在 DbMigration 配置文件中我们需要指定要使用的代码生成器,默认在 Migration 文件夹中查找 Configuration.cs... 内部密封类 Configuration : DbMigrationsConfiguration { public Configuration() { // 我建议禁用自动迁移,以便我们控制 // 迁移显式 AutomaticMigrationsEnabled = false; CodeGenerator = new EFExtensions.CustomCodeGenerator(); } protected override void Seed(YourApplication.Database.Context context) { // 你的自定义种子逻辑在这里 } }


V
Velyo

我发现仅在实体属性上使用 Auto-Property Initializer 就足以完成工作。

例如:

public class Thing {
    public bool IsBigThing { get; set; } = false;
}

这是一个很好的答案(帮助了我),但这不会在数据库中添加默认值,它会在代码中设置值。
是的,迁移更改后它没有在数据库中添加默认值
L
Liviu Sosu

出于某种原因,我无法解释自己,批准的答案不再适用于我。

它适用于另一个应用程序,而我正在使用的应用程序却没有。

因此,另一种但效率很低的解决方案是覆盖 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";
            }
        }