ASP.NET MVC 응용 프로그램[Required]
에서 내 모델 중 하나에 데이터 주석을 추가했습니다 . 마이그레이션을 만든 후 명령을 실행 하면 다음 오류가 발생합니다.Update-Database
‘Director’열, ‘MOVIES_cf7bad808fa94f89afa2e5dae1161e78.dbo.Movies’테이블에 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());
}
}
답변
올바르게 기억하면 다음과 같이 작동합니다.
AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false, defaultValueSql: "'John Doe'"));
참고 : defaultValueSql 매개 변수 값은 약어 SQL 문으로 처리되므로 John Doe 예와 같이 필수 값이 사실상 문자열 인 경우 값을 작은 따옴표로 묶어야합니다.
답변
@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
값을 기본값으로 하지만 항상 예상되는 것은 아닙니다.
열의 기본값 만 설정하고 NULL이 아니라고 가정합니다. 분명히 모든 NULL 레코드가 사용자가 제공 한 기본값으로 업데이트 될 것이라고 기대하지는 않습니다.
따라서 제 생각에는 이것은 버그가 아니며 EF가 명시 적으로 지시하지 않은 방식으로 데이터를 업데이트하는 것을 원하지 않습니다. 개발자는 데이터로 수행 할 작업에 대해 시스템에 지시 할 책임이 있습니다.
답변
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());
}
}
답변
이 옵션이 항상 주변에 있는지 확실하지 않지만 비슷한 문제가 발생하여 다음을 사용하여 수동 업데이트를 실행하지 않고 기본값을 설정할 수 있음을 발견했습니다.
defaultValueSql: "'NY'"
나는 값이되었다 제공 오류가있어 "NY"
다음 나는 그들이 SQL 값을 기대하고 있음을 깨달았을 같은 "GETDATE()"
내가 노력 있도록 "'NY'"
하고 트릭 행한
전체 라인은 다음과 같습니다
AddColumn("TABLE_NAME", "State", c => c.String(maxLength: 2, nullable: false, defaultValueSql: "'NY'"));
이 답변 덕분에 올바른 방향으로 나아갔습니다.
답변
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);
}
답변
엔터티 속성에 Auto-Property Initializer를 사용하는 것만으로도 작업을 완료 할 수 있습니다.
예를 들면 :
public class Thing {
public bool IsBigThing { get; set; } = false;
}
답변
다른 많은 응답은 이러한 문제가 발생할 때 수동으로 개입하는 방법에 중점을 둡니다.
마이그레이션을 생성 한 후 마이그레이션에 대해 다음 변경 중 하나를 수행하십시오.
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 런타임에서 의미가 적기 때문에이 작업을 자동으로 수행하지 않습니다. null 인 경우에도 기본값 제약 조건은 평가되지 않습니다.
이 AlterColumn 문은 기본 제약 조건이 적용되는 유일한 시간입니다. SQL Server 마이그레이션 구현을 디자인 한 팀의 우선 순위가 낮아진 것 같습니다.
다음 솔루션은 속성 표기법, 모델 구성 규칙 및 열 주석을 결합하여 메타 데이터를 통해 사용자 지정 마이그레이션 코드 생성기로 전달합니다. 속성 표기법을 사용하지 않는 경우 1 단계와 2 단계를 영향을받는 각 필드에 대한 유창한 표기법으로 바꿀 수 있습니다.
여기에는 많은 기술이 있습니다. 일부 또는 전부를 자유롭게 사용하십시오. 여기 모두에게 가치가 있기를 바랍니다.
-
기본값 선언
사용할 기본값을 정의하기 위해 기존 속성을 생성하거나 용도를 변경합니다.이 예에서는 사용이 직관적이고 기존 속성이 존재할 가능성이 있으므로 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) { } } }
-
열 주석에 기본값을 삽입하는 규칙을 만듭니다. 열 주석은
열에 대한 사용자 지정 메타 데이터를 마이그레이션 스크립트 생성기로 전달하는 데 사용됩니다.
이를 위해 규칙을 사용하면 각 필드에 대해 개별적으로 지정하지 않고 여러 속성에 대해 유창한 메타 데이터를 정의하고 조작 할 수있는 방법을 단순화하는 속성 표기법의 힘을 보여줍니다.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; } } }
-
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>(); }
-
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); } } }
-
CustomCodeGenerator 등록
마지막 단계, DbMigration 구성 파일에서 사용할 코드 생성기를 지정해야합니다. 기본적으로 마이그레이션 폴더에서 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 } }