Saya telah menambahkan [Required]
anotasi data ke salah satu model saya dalam aplikasi ASP.NET MVC . Setelah membuat migrasi, menjalankan Update-Database
perintah menghasilkan kesalahan berikut:
Tidak dapat memasukkan nilai NULL ke dalam kolom 'Director', table 'MOVIES_cf7bad808fa94f89afa2e5dae1161e78.dbo.Movies'; kolom tidak mengizinkan null. UPDATE gagal. Pernyataan tersebut telah dihentikan.
Ini karena beberapa record memiliki NULL di Director
kolomnya. Bagaimana saya bisa secara otomatis mengubah nilai-nilai itu ke beberapa default (katakanlah "John Doe") direktur?
Ini model saya:
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; }
}
dan ini migrasi terakhir saya:
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());
}
}
sumber
IS NULL
check by query Anda."'John Doe'"
- Anda perlu menggunakan kutipan SQL.AlterColumn
memperbarui nilai saat ini? Ini adalah perintah DDL (bukan DML).Selain jawaban dari @webdeveloper dan @Pushpendra, Anda perlu menambahkan pembaruan ke migrasi secara manual untuk memperbarui baris yang ada. Sebagai contoh:
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")); }
Ini karena
AlterColumn
menghasilkan DDL untuk mengatur default kolom ke beberapa nilai tertentu dalam spesifikasi tabel. DDL tidak memengaruhi baris yang ada dalam database.Anda sebenarnya membuat dua perubahan pada saat yang sama (menyetel default dan menjadikan kolom NOT NULL) dan masing-masing valid secara individual, tetapi karena Anda membuat keduanya pada saat yang sama, Anda dapat mengharapkan sistem untuk ' dengan cerdas 'menyadari maksud Anda dan menyetel semua
NULL
nilai ke nilai default, tetapi ini bukanlah yang diharapkan sepanjang waktu.Misalkan Anda hanya menyetel nilai default untuk kolom, dan tidak menjadikannya NOT NULL. Anda jelas tidak mengharapkan semua catatan NULL diperbarui dengan default yang Anda berikan.
Jadi, menurut saya, ini bukan bug, dan saya tidak ingin EF memperbarui data saya dengan cara yang tidak saya perintahkan secara eksplisit. Pengembang bertanggung jawab untuk menginstruksikan sistem tentang apa yang harus dilakukan dengan data.
sumber
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()); } }
sumber
tidak yakin apakah opsi ini selalu ada tetapi baru saja mengalami masalah serupa, menemukan bahwa saya dapat mengatur nilai default tanpa menjalankan pembaruan manual menggunakan yang berikut ini
defaultValueSql: "'NY'"
Saya mendapat kesalahan ketika nilai yang diberikan
"NY"
kemudian saya menyadari bahwa mereka mengharapkan nilai SQL seperti"GETDATE()"
jadi saya mencoba"'NY'"
dan itu berhasilseluruh baris terlihat seperti ini
AddColumn("TABLE_NAME", "State", c => c.String(maxLength: 2, nullable: false, defaultValueSql: "'NY'"));
Berkat jawaban ini , membuat saya di jalur yang benar
sumber
Saya menemukan bahwa hanya menggunakan Penginisialisasi Properti Otomatis pada properti entitas sudah cukup untuk menyelesaikan pekerjaan.
Sebagai contoh:
public class Thing { public bool IsBigThing { get; set; } = false; }
sumber
Banyak dari tanggapan lain yang berfokus pada cara melakukan intervensi secara manual saat masalah ini terjadi.
Perlu diingat bahwa perubahan manual yang diterapkan ke skrip migrasi akan ditimpa jika Anda menata ulang migrasi. Untuk solusi pertama, cukup mudah untuk memperluas EF untuk menentukan nilai default pada bidang secara otomatis sebagai bagian dari generasi migrasi.
Solusi berikut menggabungkan notasi atribut, konvensi konfigurasi model, dan anotasi kolom untuk meneruskan metadata ke generator kode Migrasi kustom. Langkah 1 dan 2 dapat diganti dengan notasi fasih untuk setiap bidang yang terpengaruh jika Anda tidak menggunakan notasi atribut.
Ada banyak teknik dalam bermain di sini, silakan gunakan beberapa atau semua, saya harap ada nilai untuk semua orang di sini
Deklarasikan Nilai Default
Buat atau gunakan kembali atribut yang ada untuk menentukan nilai default yang akan digunakan, untuk contoh ini kita akan membuat atribut baru bernama DefaultValue yang mewarisi dari ComponentModel.DefaultValueAttribute, karena penggunaannya intuitif dan ada kemungkinan basis kode sudah menerapkan atribut ini. Dengan implementasi ini, Anda hanya perlu menggunakan atribut khusus ini untuk mengakses DefaultValueSql yang berguna untuk tanggal dan skenario khusus lainnya.
Penerapan
[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; }
Definisi Atribut
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) { } } }
Buat konvensi untuk memasukkan nilai default ke dalam anotasi kolom Anotasi
kolom digunakan untuk meneruskan metadata khusus tentang kolom ke generator skrip migrasi.
Menggunakan konvensi untuk melakukan ini mendemonstrasikan kekuatan di balik notasi atribut untuk menyederhanakan bagaimana metadata yang lancar dapat didefinisikan dan dimanipulasi untuk banyak properti daripada menentukannya secara individual untuk setiap bidang.
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; } } }
Tambahkan Konvensi ke DbContext
Ada banyak cara untuk mencapai ini, saya ingin mendeklarasikan konvensi sebagai langkah kustom pertama dalam logika ModelCreation saya, ini akan berada di kelas DbContext Anda.
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>(); }
Mengganti MigrationCodeGenerator
Setelah anotasi tersebut telah diterapkan ke definisi kolom dalam model, kita perlu memodifikasi generator skrip migrasi untuk menggunakan anotasi tersebut. Untuk ini kita akan mewarisi dari
System.Data.Entity.Migrations.Design.CSharpMigrationCodeGenerator
karena kita hanya perlu menyuntikkan sedikit perubahan.Setelah kami memproses anotasi khusus kami, kami perlu menghapusnya dari definisi kolom untuk mencegahnya diserialkan ke hasil akhir.
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); } } }
Daftarkan CustomCodeGenerator
Langkah terakhir, di file Konfigurasi DbMigration kita perlu menentukan Penghasil Kode yang akan digunakan, cari Configuration.cs di folder Migrasi Anda secara default ...
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 } }
sumber
Sejak EF Core 2.1, Anda dapat menggunakan
MigrationBuilder.UpdateData
untuk mengubah nilai sebelum mengubah kolom (lebih bersih daripada menggunakan SQL mentah):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); }
sumber
Untuk beberapa alasan, saya tidak dapat menjelaskan diri saya sendiri, jawaban yang disetujui tidak lagi berfungsi untuk saya.
Ini berfungsi di aplikasi lain, di aplikasi yang saya kerjakan itu tidak.
Jadi, solusi alternatif, tetapi cukup tidak efisien , akan menimpa Metode SaveChanges () seperti yang ditunjukkan di bawah ini. Metode ini harus berada di kelas Konteks.
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"; } }
sumber