Entity Framework移行の必須フィールドのデフォルト値は?


91

ASP.NET MVCアプリケーションの[Required]モデルの1つにデータアノテーションを追加しました。移行を作成した後、コマンドを実行すると次のエラーが発生します。Update-Database

列「Director」、テーブル「MOVIES_cf7bad808fa94f89afa2e5dae1161e78.dbo.Movies」に値NULLを挿入できません。列はnullを許可しません。UPDATEは失敗します。ステートメントは終了されました。

これは、一部のレコードの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());
    }
}

回答:


74

私が正しく覚えていれば、このようなものがうまくいくはずです:

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

注:defaultValueSqlパラメーター値は逐語的なSQLステートメントとして扱われるため、必要な値が事実上John Doeの例のように文字列である場合、値の前後に一重引用符が必要です。


9
私もそう考えましたが、それは既存のレコードではうまくいかないようです。したがって、まだエラーが発生します。
Andriy Drozdyuk 2012

@drozzy多分これはバグのようです:EF 4.3.1移行例外-AlterColumn defaultValueSqlは異なるテーブルに対して同じデフォルトの制約名を作成IS NULLしますクエリによるチェックで行を更新できます。
webdeveloper 2012

興味深いですが、彼らが何を話しているのか理解できません。ただし、これがバグの場合、はい、それは理にかなっています。
Andriy Drozdyuk 2012

6
私はそれがそうあるべきだと思います:"'John Doe'"-あなたはSQL引用符を使う必要があります。
Sean

1
@webdeveloper、それはバグだとは思いません、なぜAlterColumn現在の値を更新するのですか?これはDDL(DMLではない)コマンドです。
アントン

110

@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"));
}

これは、AlterColumnDDLを生成して、列のデフォルトをテーブル仕様の特定の値に設定するためです。DDLは、データベース内の既存の行には影響しません。

実際には2つの変更を同時に行っており(デフォルトを設定し、列をNOT NULLにします)、それらのそれぞれが個別に有効ですが、同時に2つを行っているので、システムは 'インテリジェントに」あなたの意図を実現し、すべてのNULL値をデフォルト値に設定しますが、これは常に期待されていることではありません。

列のデフォルト値を設定するだけで、NOT NULLにしないとします。すべてのNULLレコードが、指定したデフォルトで更新されることを期待しているわけではありません。

したがって、私の意見では、これはバグではなく、EFが明示的に指示しない方法でデータを更新したくないのです。開発者は、データをどう処理するかをシステムに指示する責任があります。


17
グーグルを介してこの答えを見つける人々のために:私はこれをEF6で試したところ、更新ステートメントは必要なくなったようです(もう)。結局、彼らはそれをバグだと思ったのでしょう。
EPLKleijntjens 2014年

3
それを保証することもできます。null可能フィールドでもデフォルト値が必要な場合は、まずデフォルト値を使用してそれをnot null可能に変更してから、null可能に戻します。null可能ではないフィールドを子クラスに追加したときに非常に便利です:)
Wouter Schut

1
説明のスポット。AlterColumn()は列定義を変更するだけです。既存のレコードにはまったく影響しません
Korayem

10
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());       
    }
}

2
ええと...ありがとう、でも@webdeveloperの答えとどう違うのですか?
Andriy Drozdyuk 2012

1
デフォルト値パラメーターを追加する必要がある場所は示されていません
Pushpendra

1
@Pushpendra、開発者がかつてのことを忘れがちな傾向があるのはおかしいですが、彼らはあまり知りませんでした。私はすべてのレベルを満たす詳細な答えが好きです。よくできました!
有用なビー

5

このオプションが常に存在するかどうかはわかりませんが、同様の問題が発生しただけで、次のコマンドを使用して手動で更新することなくデフォルト値を設定できることがわかりました

defaultValueSql: "'NY'"

提供された値が "NY"そのときにSQL値を期待していることに気付いた"GETDATE()"ので、私は試しまし"'NY'"たがトリックを行いました

行全体はこのようになります

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

この答えのおかげで、私は正しい軌道に乗った


2

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

1

エンティティプロパティでAuto-Property Initializerを使用するだけで十分です。

例えば:

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

2
それは良い答えです(私を助けました)が、これはデータベースにデフォルト値を追加せず、コードで値を設定します。
chris313​​89 2017

移行の後でデータベースにデフォルト値が追加されませんでした
Chetan Chaudhari

1

他の応答の多くは、これらの問題が発生したときに手動で介入する方法に焦点を当てています。

移行を生成した後、移行に対して次のいずれかの変更を実行します。

  1. 列定義を変更して、defaultValueまたはdefaultSqlステートメントを含めます。
    AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false, default: ""));

  2. SQLステートメントを挿入して、AlterColumnの前に既存の列を事前入力します。
    Sql("UPDATE dbo.Movies SET Director = '' WHERE Director IS NULL");

移行を再足場する場合、移行スクリプトに適用された手動の変更は上書きされることに注意してください。最初のソリューションの場合、EFを拡張して、移行生成の一部としてフィールドのデフォルト値を自動的に定義することは非常に簡単です。

注:デフォルト値の実装はRDBMSプロバイダーごとに異なるため、EFはこれを自動的に行いませんが、各行の挿入によって各プロパティの現在の値が提供されるため、純粋なEFランタイムではデフォルト値の意味が少なくなります。 nullであっても、デフォルト値の制約は評価されません。
このAlterColumnステートメントは、既定の制約が機能する唯一のタイミングです。これは、SQL Server移行の実装を設計したチームにとっては優先度が低くなると思います。

次のソリューションでは、属性表記、モデル構成規則、および列注釈を組み合わせて、メタデータをカスタムの移行コードジェネレーターに渡します。属性表記を使用していない場合は、影響を受ける各フィールドのステップ1と2を流暢な表記に置き換えることができます。
ここには多くのテクニックがあります。一部またはすべてを自由に使用してください。ここにいるすべての人に価値があることを願っています


  1. デフォルト値の宣言
    既存の属性を作成または再利用して、使用するデフォルト値を定義します。この例では、使用法が直感的であり、既存の可能性があるため、ComponentModel.DefaultValueAttributeから継承するDefaultValueという新しい属性を作成します。コードベースはすでにこの属性を実装しています。この実装では、この特定の属性を使用してDefaultValueSqlにアクセスするだけでよく、日付やその他のカスタムシナリオに役立ちます。

    実装

    [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; }

    属性の定義

    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) { }
        }
    }
  2. 列アノテーションにデフォルト値を挿入する規則を作成する
    列アノテーションは、列に関するカスタムメタデータを移行スクリプトジェネレーターに渡すために使用されます。
    慣習を使用してこれを行うと、属性表記の背後にある力が発揮され、フィールドごとに個別に指定するのではなく、多くのプロパティに対して流暢なメタデータを定義および操作する方法が簡素化されます。

    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;
            }
        }
    
    }
  3. DbContextへの規則の追加
    これを達成するには多くの方法があります。私はModelCreationロジックの最初のカスタムステップとして規則を宣言します。これはDbContextクラスにあります。

    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>();
    
    }
  4. MigrationCodeGeneratorのオーバーライド
    これらの注釈がモデル内の列定義に適用されたので、これらの注釈を使用するように移行スクリプトジェネレーターを変更する必要があります。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);
            }
    
    
        }
    }
  5. CustomCodeGeneratorの
    最後のステップを登録します。使用するコードジェネレーターを指定する必要があるDbMigration構成ファイルで、デフォルトでMigrationフォルダーでConfiguration.csを探します...

    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
        }
    }

0

どういうわけか、私は自分自身を説明することができなかったので、承認された答えはもはや私にとってはうまくいきません。

それは別のアプリで機能しましたが、私が機能しているアプリでは機能しません。

したがって、代替の、しかし非常に非効率的なソリューションは、次に示すように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";
            }
        }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.