Dodałem [Required]
adnotację danych do jednego z moich modeli w aplikacji ASP.NET MVC . Po utworzeniu migracji uruchomienie Update-Database
polecenia powoduje następujący błąd:
Nie można wstawić wartości NULL do kolumny „Director”, tabela „MOVIES_cf7bad808fa94f89afa2e5dae1161e78.dbo.Movies”; kolumna nie zezwala na wartości null. UPDATE nie powiodło się. Oświadczenie zostało zakończone.
Jest to spowodowane tym, że niektóre rekordy mają w swoich Director
kolumnach wartość NULL . Jak mogę automatycznie zmienić te wartości na jakiegoś domyślnego reżysera (np. „John Doe”)?
Oto mój model:
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; }
}
a oto moja ostatnia migracja:
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());
}
}
źródło
IS NULL
sprawdzaniem za pomocą zapytania."'John Doe'"
- musisz używać cudzysłowów SQL.AlterColumn
aktualizować aktualne wartości? Jest to polecenie DDL (nie DML).Oprócz odpowiedzi z @webdeveloper i @Pushpendra, musisz ręcznie dodać aktualizacje do migracji, aby zaktualizować istniejące wiersze. Na przykład:
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")); }
Dzieje się tak, ponieważ
AlterColumn
generuje DDL, aby ustawić wartość domyślną kolumny na określoną wartość w specyfikacji tabeli. DDL nie wpływa na istniejące wiersze w bazie danych.W rzeczywistości wprowadzasz dwie zmiany w tym samym czasie (ustawiając wartość domyślną i ustawiając kolumnę jako NIE NULL) i każda z nich jest ważna indywidualnie, ale ponieważ wprowadzasz dwie zmiany w tym samym czasie, możesz oczekiwać, że system ' inteligentnie „zrealizuj swój zamiar i ustaw wszystko
NULL
wartości na wartości domyślne, ale nie jest to oczekiwane przez cały czas.Załóżmy, że ustawiasz tylko domyślną wartość kolumny i nie ustawiasz jej jako NIE NULL. Oczywiście nie oczekujesz, że wszystkie rekordy NULL zostaną zaktualizowane zgodnie z podanymi wartościami domyślnymi.
Tak więc, moim zdaniem, nie jest to błąd i nie chcę, aby EF aktualizował moje dane w sposób, którego wyraźnie mu nie nakazuję. Deweloper jest odpowiedzialny za poinstruowanie systemu, co ma zrobić z danymi.
źródło
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()); } }
źródło
nie jestem pewien, czy ta opcja była zawsze dostępna, ale właśnie napotkałem podobny problem, stwierdziłem, że udało mi się ustawić wartość domyślną bez uruchamiania jakichkolwiek aktualizacji ręcznych za pomocą następującego
defaultValueSql: "'NY'"
Wystąpił błąd, gdy podana wartość została
"NY"
następnie zdałem sobie sprawę, że spodziewają się wartości SQL,"GETDATE()"
tak jak próbowałem"'NY'"
i to załatwiło sprawęcała linia wygląda tak
AddColumn("TABLE_NAME", "State", c => c.String(maxLength: 2, nullable: false, defaultValueSql: "'NY'"));
Dzięki tej odpowiedzi trafiłem na właściwą ścieżkę
źródło
Zauważyłem, że samo użycie Auto-Property Initializer na właściwości jednostki wystarczy, aby wykonać zadanie.
Na przykład:
public class Thing { public bool IsBigThing { get; set; } = false; }
źródło
Wiele innych odpowiedzi koncentruje się na ręcznej interwencji, gdy wystąpią te problemy.
Należy pamiętać, że ręczne zmiany zastosowane do skryptu migracji zostaną nadpisane, jeśli ponownie utworzysz szkielet migracji. W przypadku pierwszego rozwiązania dość łatwo jest rozszerzyć EF, aby automatycznie zdefiniować wartość domyślną w polu w ramach generowania migracji.
Poniższe rozwiązanie łączy notację atrybutów, konwencje konfiguracji modelu i adnotacje kolumn w celu przekazania metadanych do niestandardowego generatora kodu migracji. Kroki 1 i 2 można zastąpić płynną notacją dla każdego pola, którego dotyczy problem, jeśli nie używasz notacji atrybutów.
W grze jest wiele technik, możesz użyć niektórych lub wszystkich, mam nadzieję, że każdy tutaj ma wartość
Zadeklaruj wartość domyślną
Utwórz lub zmień przeznaczenie istniejącego atrybutu, aby zdefiniować wartość domyślną do użycia, w tym przykładzie utworzymy nowy atrybut o nazwie DefaultValue, który dziedziczy po ComponentModel.DefaultValueAttribute, ponieważ użycie jest intuicyjne i istnieje prawdopodobieństwo, że istnieje bazy kodu już implementują ten atrybut. W przypadku tej implementacji wystarczy użyć tego konkretnego atrybutu, aby uzyskać dostęp do DefaultValueSql, co jest przydatne w przypadku dat i innych niestandardowych scenariuszy.
Realizacja
[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 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) { } } }
Utwórz konwencję wstrzyknięcia wartości domyślnej do adnotacji
kolumn. Adnotacje kolumn są używane do przekazywania niestandardowych metadanych dotyczących kolumn do generatora skryptów migracji.
Użycie w tym celu konwencji demonstruje moc kryjącą się za notacją atrybutów, aby uprościć sposób definiowania i manipulowania płynnymi metadanymi dla wielu właściwości, zamiast określania ich indywidualnie dla każdego pola.
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; } } }
Dodaj konwencję do DbContext
Istnieje wiele sposobów, aby to osiągnąć. Lubię deklarować konwencje jako pierwszy niestandardowy krok w mojej logice ModelCreation, będzie to w Twojej klasie 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>(); }
Zastąp MigrationCodeGenerator
Teraz, gdy te adnotacje zostały zastosowane do definicji kolumn w modelu, musimy zmodyfikować generator skryptów migracji, aby używał tych adnotacji. W tym celu odziedziczymy po tym,
System.Data.Entity.Migrations.Design.CSharpMigrationCodeGenerator
jak musimy wprowadzić minimalną ilość zmiany.Po przetworzeniu naszej niestandardowej adnotacji musimy usunąć ją z definicji kolumny, aby zapobiec jej serializacji do ostatecznego wyniku.
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); } } }
Zarejestruj CustomCodeGenerator
Ostatni krok, w pliku konfiguracyjnym DbMigration musimy określić generator kodu, który ma być używany, domyślnie poszukaj pliku Configuration.cs w folderze migracji ...
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 } }
źródło
Od EF Core 2.1 można użyć
MigrationBuilder.UpdateData
do zmiany wartości przed zmianą kolumny (czystsze niż przy użyciu surowego kodu 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); }
źródło
Z jakiegoś powodu nie mogłem się wytłumaczyć, zaakceptowana odpowiedź już mi nie odpowiada.
Zadziałało w innej aplikacji, ale na tej, na której pracuję, nie działa.
Tak więc alternatywnym, ale dość nieefektywnym rozwiązaniem byłoby zastąpienie metody SaveChanges (), jak pokazano poniżej. Ta metoda powinna znajdować się w klasie 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"; } }
źródło