wake-up-neo.net

Standardwert für Erforderliche Felder in Entity Framework-Migrationen?

Ich habe die [Required]-Datenanmerkung zu einem meiner Modelle in einer ASP.NET-MVC-Anwendung hinzugefügt. Nach dem Erstellen einer Migration führt das Ausführen des Befehls Update-Database zu dem folgenden Fehler:

Der Wert NULL kann nicht in die Spalte 'Director', Tabelle .__ eingefügt werden. "MOVIES_cf7bad808fa94f89afa2e5dae1161e78.dbo.Movies"; Spalte nicht Nullen zulassen. UPDATE schlägt fehl Die Anweisung wurde beendet.

Dies liegt daran, dass einige Datensätze NULL in ihren Director-Spalten haben. Wie kann ich diese Werte automatisch in einen Standard-Regisseur (zB "John Doe") ändern?

Hier ist mein Modell:

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

und hier ist meine letzte Migration:

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

Wenn ich mich recht erinnere, sollte so etwas funktionieren:

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

Neben der Antwort von @webdeveloper und @Pushpendra müssen Sie der Migration manuell Updates hinzufügen, um vorhandene Zeilen zu aktualisieren. Zum Beispiel:

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

Dies liegt daran, dass AlterColumn DDL erzeugt, um den Standardwert der Spalte auf einen bestimmten Wert in der Tabellenspezifikation festzulegen. Die DDL wirkt sich nicht auf vorhandene Zeilen in der Datenbank aus.

Sie nehmen tatsächlich zwei Änderungen gleichzeitig vor (indem Sie die Standardeinstellung festlegen und die Spalte NICHT NULL setzen). Jede davon ist einzeln gültig. Da Sie jedoch beide gleichzeitig vornehmen, können Sie davon ausgehen, intelligent 'verwirklichen Sie Ihre Absicht und setzen Sie alle NULL-Werte auf den Standardwert, dies ist jedoch nicht das, was Sie immer erwarten.

Angenommen, Sie legen nur den Standardwert für die Spalte fest und machen ihn nicht NICHT NULL. Sie erwarten natürlich nicht, dass alle NULL-Datensätze mit dem von Ihnen angegebenen Standardwert aktualisiert werden.

Meiner Meinung nach handelt es sich also nicht um einen Fehler, und ich möchte nicht, dass EF meine Daten auf die Art und Weise aktualisiert, die ich nicht ausdrücklich erkläre. Der Entwickler ist dafür verantwortlich, dem System mitzuteilen, was mit den Daten zu tun ist.

101
Iravanchi
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());       
    }
}
9
Pushpendra

ich bin mir nicht sicher, ob diese Option immer verfügbar war, aber ich habe gerade ein ähnliches Problem festgestellt. Ich konnte den Standardwert festlegen, ohne manuelle Updates auszuführen

defaultValueSql: "'NY'"

Ich habe eine Fehlermeldung erhalten, als der angegebene Wert "NY" War. Dann habe ich festgestellt, dass sie einen SQL-Wert wie "GETDATE()" erwarten, also habe ich versucht, "'NY'" Und das hat den Trick gemacht

die gesamte Zeile sieht so aus

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

Dank diese Antwort bin ich auf dem richtigen Weg

5
workabyte

Ich habe festgestellt, dass die Verwendung der Auto-Property-Initialisierung für eine Entitätseigenschaft ausreicht, um die Aufgabe zu erledigen.

Zum Beispiel: 

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

Seit EF Core 2.1 können Sie MigrationBuilder.UpdateData verwenden, um Werte zu ändern, bevor Sie die Spalte ändern (sauberer als bei Verwendung von unformatiertem 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);
}
0
Antoine Robin

Viele der anderen Antworten befassen sich mit dem manuellen Eingreifen, wenn diese Probleme auftreten.

Führen Sie nach dem Generieren der Migration eine der folgenden Änderungen an der Migration durch:

  1. Ändern Sie die Spaltendefinition so, dass sie eine defaultValue- oder defaultSql-Anweisung enthält:
    AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false, default: ""));

  2. Injizieren Sie eine SQL-Anweisung, um die vorhandenen Spalten vor der AlterColumn vorzufüllen:
    Sql("UPDATE dbo.Movies SET Director = '' WHERE Director IS NULL");

Beachten Sie, dass manuelle Änderungen an einem Migrationsskript überschrieben werden, wenn Sie die Migration erneut durchführen. Für die erste Lösung ist es ziemlich einfach, EF zu erweitern, um einen Standardwert für ein Feld automatisch als Teil der Migrationsgenerierung zu definieren.

HINWEIS: EF erledigt dies nicht automatisch für Sie, da die Standardwertimplementierung für jeden RDBMS-Anbieter unterschiedlich wäre, sondern auch, weil Standardwerte in einer reinen EF-Laufzeit weniger Bedeutung haben, da jede Zeileneinfügung den aktuellen Wert für jede Eigenschaft liefert. Auch wenn es null ist, wird die Standardwerteinschränkung niemals ausgewertet.
Diese AlterColumn-Anweisung ist das einzige Mal, dass die Standardeinschränkung zum Tragen kommt. Ich denke, dies hat für das Team, das die SQL Server-Migrationsimplementierung entworfen hat, eine niedrigere Priorität.

In der folgenden Lösung werden Attributnotation, Modellkonfigurationskonventionen und Spaltenanmerkungen kombiniert, um Metadaten an einen benutzerdefinierten Migrationscodegenerator zu übergeben. Die Schritte 1 und 2 können für jedes betroffene Feld durch eine fließende Notation ersetzt werden, wenn Sie keine Attributnotation verwenden.
Hier gibt es eine Menge Techniken, zögern Sie nicht, einige oder alle zu verwenden, ich hoffe, dass hier für alle etwas dabei ist


  1. Deklarieren Sie den Standardwert
    Erstellen Sie ein vorhandenes Attribut oder verwenden Sie es erneut, um den zu verwendenden Standardwert zu definieren. In diesem Beispiel erstellen wir ein neues Attribut mit dem Namen DefaultValue, das von ComponentModel.DefaultValueAttribute erbt, da die Verwendung intuitiv ist und die Möglichkeit besteht, dass dies der Fall ist vorhandene Codebasen implementieren dieses Attribut bereits. Mit dieser Implementierung müssen Sie nur dieses bestimmte Attribut verwenden, um auf DefaultValueSql zuzugreifen, das für Daten und andere benutzerdefinierte Szenarien nützlich ist.

    Implementierung

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

    Attribut Definition

    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. Erstellen Sie eine Konvention, um den Standardwert in die Spaltenanmerkungen einzufügen
    Spaltenanmerkungen werden verwendet, um benutzerdefinierte Metadaten zu Spalten an den Migrationsskriptgenerator weiterzuleiten.
    Die Verwendung einer Konvention demonstriert die Leistungsfähigkeit der Attributnotation, um zu vereinfachen, wie fließende Metadaten für viele Eigenschaften definiert und bearbeitet werden können, anstatt sie für jedes Feld einzeln anzugeben.

    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. Fügen Sie die Konvention zum DbContext hinzu
    Es gibt viele Möglichkeiten, dies zu erreichen. Ich möchte die Konventionen als ersten benutzerdefinierten Schritt in meiner ModelCreation-Logik deklarieren. Dies wird in Ihrer DbContext-Klasse sein.

    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. Überschreibe den MigrationCodeGenerator
    Nachdem diese Annotationen auf die Spaltendefinitionen im Modell angewendet wurden, müssen Sie den Migrationsskriptgenerator ändern, um diese Annotationen zu verwenden. Dafür erben wir vom System.Data.Entity.Migrations.Design.CSharpMigrationCodeGenerator da wir nur eine minimale Menge an Veränderung einbringen müssen.
    Nachdem wir unsere benutzerdefinierte Anmerkung verarbeitet haben, müssen wir sie aus der Spaltendefinition entfernen, um zu verhindern, dass sie für die endgültige Ausgabe serialisiert wird.

    Weitere Informationen zur Verwendung finden Sie im Basisklassencode: 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. Registrieren Sie den CustomCodeGenerator
    Im letzten Schritt müssen wir in der DbMigration-Konfigurationsdatei den zu verwendenden Codegenerator angeben und standardmäßig in Ihrem Migrationsordner nach Configuration.cs suchen ...

    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
Chris Schaller

Aus irgendeinem Grund konnte ich mir nicht erklären, dass die bestätigte Antwort für mich nicht mehr funktioniert.

Es funktionierte bei einer anderen App, bei der ich arbeite, funktioniert es nicht.

Eine Alternative, aber ziemlich ineffizient, wäre die Lösung, die SaveChanges () -Methode wie folgt zu überschreiben. Diese Methode sollte in der Context-Klasse enthalten sein.

    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";
            }
        }
0
Liviu Sosu