【问题标题】:SQL column default value with Entity Framework实体框架的 SQL 列默认值
【发布时间】:2015-01-18 06:01:20
【问题描述】:

我正在尝试将 Code-First EF6 与默认 SQL 值一起使用。

例如,我有一个“CreatedDate”列/属性不为空,SQL 中的默认值为“getdate()”

如何在我的代码模型中表示这一点?目前我有:

<DatabaseGenerated(DatabaseGeneratedOption.Computed)>
Public Property CreatedDate As DateTime

这是否可行,或者即使实际列不应为空,我是否需要使用可为空,因此 EF 在尚未设置时不会发送值:

<DatabaseGenerated(DatabaseGeneratedOption.Computed)>
Public Property CreatedDate As DateTime?

或者有更好的解决方案吗?

我不希望 EF 处理我的默认值 - 我知道这对我来说是可用的,但在我目前的情况下是不可能的。

【问题讨论】:

  • 感谢@shalithasenanayaka,但您提到的答案是由应用程序中的逻辑计算的,我最初的问题(虽然回答很长)是关于允许 SQL 提供默认值。您链接到的答案不是解决此问题的可行方法。还是谢谢。

标签: .net entity-framework entity-framework-6


【解决方案1】:

目前在 EF6 中没有一个属性来定义用于某个属性默认值的数据库函数。您可以对 Codeplex 进行投票以实现它:

https://entityframework.codeplex.com/workitem/44

实现类似功能的公认方法是将Computed 属性与Migrations 一起使用,您可以在其中指定默认数据库函数。

您的类在 C# 中可能如下所示:

public class MyEntity
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime Created { get; set; }
}

计算的属性不必是可空的。

然后您必须运行迁移并手动修改它以包含默认 SQL 函数。迁移可能如下所示:

public partial class Initial : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.MyEntities",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Name = c.String(),
                    Created = c.DateTime(nullable: false, defaultValueSql: "GetDate()"),
                })
            .PrimaryKey(t => t.Id);

    }

    public override void Down()
    {
        DropTable("dbo.MyEntities");
    }
}

您会注意到 defaultValueSql 函数。这是让计算工作的关键

【讨论】:

    【解决方案2】:

    接受的答案对于 EF6 是正确的,我只添加 EF Core 解决方案; (我的解决方案也侧重于更改默认值,而不是第一次正确创建它

    EF Core 中仍然没有 Data-Attribute。

    而且您仍然必须使用 Fluent API;它确实有一个HasDefaultValue

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Rating)
            .HasDefaultValue(3);
    }
    

    注意,NULL 情况下还有HasDefaultValueSql

            .HasDefaultValueSql("NULL");
    

    您还可以使用迁移UpDown 方法,您可以更改defaultValuedefaultValueSql,但您可能需要先删除索引。这是一个例子:

    public partial class RemovingDefaultToZeroPlantIdManualChange : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropIndex(
                name: "IX_TABLE_NAME_COLUMN_NAME",
                table: "TABLE_NAME"
            );
    
            migrationBuilder.AlterColumn<int>(
                name: "COLUMN_NAME",
                table: "TABLE_NAME",
                nullable: true,
                //note here, in the Up method, I'm specifying a new defaultValue:
                defaultValueSql: "NULL",
                oldClrType: typeof(int));
    
            migrationBuilder.CreateIndex(
                name: "IX_TABLE_NAME_COLUMN_NAME",
                table: "TABLE_NAME",
                column: "COLUMN_NAME"
            );
        }
    
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropIndex(
                name: "IX_TABLE_NAME_COLUMN_NAME",
                table: "TABLE_NAME"
            );
    
            migrationBuilder.AlterColumn<int>(
                name: "COLUMN_NAME",
                table: "TABLE_NAME",
                nullable: true,
                //note here, in the Down method, I'll restore to the old defaultValue:
                defaultValueSql: "0",
                oldClrType: typeof(int));
    
            migrationBuilder.CreateIndex(
                name: "IX_TABLE_NAME_COLUMN_NAME",
                table: "TABLE_NAME",
                column: "COLUMN_NAME"
            );
    
    
        }
    }
    

    【讨论】:

      【解决方案3】:

      [mysql]

      对于那些不想在每次数据库更新后使用计算并重写它的人,我为数据库部分类编写了扩展方法。当然,有些东西需要改进或添加,但现在已经足够我们使用了,享受吧。

      考虑到,由于 database_schema 访问它不是最快的,而且您需要具有与表名相同的实体名称(或以某种方式重写)。

          public static bool GetDBDefaults(object entity)
          {
              try
              {
                  string table_name = entity.GetType().Name;
      
                  string q = $"select column_name, column_default from information_schema.columns where column_default is not null and table_schema not in ('information_schema', 'sys', 'performance_schema', 'mysql') and table_name = '{table_name}' order by table_schema, table_name, ordinal_position;";
      
                  List<DBDefaults> dbDefaults = new List<DBDefaults>();
                  using (DatabaseModelFull db = new DatabaseModelFull())
                  {
                      dbDefaults = db.Database.SqlQuery<DBDefaults>(q).ToList();
                  }
      
                  Type myType = entity.GetType();
                  IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
                  IList<FieldInfo> fields = new List<FieldInfo>(myType.GetFields());
      
                  foreach (var dbDefault in dbDefaults)
                  {
                      var prop = props.SingleOrDefault(x => x.Name == dbDefault.column_name);
      
                      if (prop != null)
                      {
                          if (dbDefault.column_default.Equals("CURRENT_TIMESTAMP"))
                              prop.SetValue(entity, System.Convert.ChangeType(DateTime.Now, prop.PropertyType));
                          else
                              prop.SetValue(entity, System.Convert.ChangeType(dbDefault.column_default, prop.PropertyType));
                          continue;
                      }
      
                      var field = fields.SingleOrDefault(x => x.Name == dbDefault.column_name);
      
                      if (field != null)
                      {
                          if (dbDefault.column_default.Equals("CURRENT_TIMESTAMP"))
                              field.SetValue(entity, System.Convert.ChangeType(DateTime.Now, field.FieldType));
                          else
                              field.SetValue(entity, System.Convert.ChangeType(dbDefault.column_default, field.FieldType));
                      }
                  }
                  return true;
              }
              catch
              {
                  return false;
              }
          }
      
      
          public class DBDefaults
          {
              public string column_name { get; set; }
              public string column_default { get; set; }
          }
      

      【讨论】:

        【解决方案4】:

        在遇到同样问题后添加此解决方案。我想使用 SQL Server 中定义的默认值,而不必在 C# 代码中设置默认值。

        此行为由 DatabaseGeneratedOption 值控制。 EF 使用默认值、NULL 值还是指定值取决于使用的选项。以下是为每个选项创建新数据库条目以及是否使用默认值的简化示例。

        // Item database declaration
        public Item()
        {
            public string id { get; set; }
            public string description { get; set }
        }
        
        // Add item to database code.  First one uses the default value, the second is
        // overriding it using the specified value.  Anything inside brackets uses your
        // database context and class definition objects.
        var itemToAdd1 = new [dbClass].Item
        {
            id = "CodeTest1"
        };
        var itemToAdd2 = new new[DBClass].Item
        {
            id = "CodeTest2",
            description = "Default value override in code"
        };
        [dbContext].Add(itemToAdd1);
        [dbContext].Add(itemToAdd2);
        [dbContext].SaveChanges();
        
        
        // The behavior changes based on the DatabaseGeneratedOption setting on the database
        // class definition for the description property.
        
        // DatabaseGeneratedOption:  None
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public string description { get; set; }
        
        Result:
        id = "CodeTest1",                                   // First item, using default value
        description = NULL                                  // Code sent null value and SQL used it instead of the default
        
        id = "CodeTest2",                                   // Second item, overriding description in code
        description = "Default value override in code"      // Code override value was used by SQL as expected
        
        
        // DatabaseGeneratedOption:  Identity
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public string description { get; set; }
        
        Result:
        id = "CodeTest1",                                   // First item, using default value
        description = "SQL Default Value"                   // Code did not send any value and SQL used the DB default value as expected
        
        id = "CodeTest2",                                   // Second item, overriding description in code
        description = "Default value override in code"      // Code override value was used by SQL as expected
        
        
        // DatabaseGeneratedOption:  Computed
        [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
        public string description { get; set; }
        
        Result:
        id = "CodeTest1",                                   // First item, using default value
        description = "SQL Default Value"                   // Code did not send any value and SQL used the DB default value as expected
        
        id = "CodeTest2",                                   // Second item, overriding description in code
        description = "SQL Default Value"                   // The SQL default value was still used despite setting this property in code.
        

        TLDR: DatabaseGeneratedOption.Identity 应该为您提供您正在寻找的结果。

        【讨论】:

          【解决方案5】:

          这是我的解决方案。我在 .Net Core 3.1 DLL 的 EF Core DbContext 中使用了它。它适用于自动迁移,并使用一个属性,该属性通过反射进行搜索。该属性包含默认的 sql 值,然后在 OnModelCreating() 中设置。

          • 第 1 步 - 添加新属性
          [AttributeUsage(AttributeTargets.Property)]
          public class DefaultValueSqlAttribute : Attribute
          {
              public string DefaultValueSql { get; private set; } = "";
              
              public DefaultValueSqlAttribute(string defaultValueSql)
              {
                  DefaultValueSql = defaultValueSql;
              }
          }
          
          • 第 2 步 - 在数据类中使用该属性
          public class Entity
          {
              [Key]
              [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
              [Column("Id", Order=0)]
              [DefaultValueSql("newId()")]
              public Guid Id { get; set; }
          
          
              [Column("DateCreated", Order = 100)]
              [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
              [DefaultValueSql("GETUTCDATE()")]
              public DateTime DateCreated { get; set; } = DateTime.UtcNow;
          }
          
          [Table("tbl_User")]
          public class User: Entity
          {
              [Required]
              [Column(Order = 1)]
              public string EMail { get; set; }
          
              [Column(Order = 2)]
              public string Name { get; set; }
          
              [Column(Order = 3)]
              public string Forename { get; set; }
          
              [Column(Order = 4)]
              public string Street { get; set; }
          
              [Column(Order = 5)]
              public string Postalcode { get; set; }
          
              [Column(Order = 6)]
              public string MobileNumber { get; set; }
          }
          
          • 第 3 步 - 将您的类添加到 DbContext
          public DbSet<User> tbl_User { get; set; }
          
          • 第 4 步 - 将此代码添加到您的 DbContext(您必须根据需要对其进行更改...) 它假定所有相关的数据类都存在于一个特定的程序集和一个特定的命名空间中。在我的例子中,它是一个 .NetStandard 2.0 DLL。
          protected override void OnModelCreating(ModelBuilder mb)
          {
              //Uncomment this line, if you want to see what is happening when you fire Add-Migration
              //Debugger.Launch();
          
              base.OnModelCreating(mb);
          
              OnModelCreatingAddDefaultSqlValues(mb);
          }
          
          private void OnModelCreatingAddDefaultSqlValues(ModelBuilder mb)
          {
              var assemblyName = "Ik.Shared";
              var nameSpace = "Ik.Shared.Entities";
          
              var asm = Assembly.Load(assemblyName);
          
              //Read all types in the assembly Ik.Shared, that are in the namespace Ik.Shared.Entities
              List<Type> types = asm.GetTypes().Where(p => p.Namespace == nameSpace).ToList();
              //Read all properties in DatabaseContext, that are of type DbSet<>
              var dbSets = typeof(DatabaseContext).GetProperties().Where(p => p.PropertyType.Name.ToLower().Contains("dbset")).ToList();
              //A list of types, that are used as a generic argument in a DbSet<T>
              List<Type> dbSetTypes = new List<Type>();
              foreach (PropertyInfo pi in dbSets)
              {
                  //Add the type of the generic argument from DbSet<T>
                  dbSetTypes.Add(pi.PropertyType.GetGenericArguments()[0]);
              }
          
              //For all types in Ik.Shared
              foreach (Type t in types)
              {
                  //If a type inherited from Entity AND the type itself is not Entity AND the type was used as DbSet<Type> in the DbContext
                  if (typeof(Entity).IsAssignableFrom(t) && t.Name != nameof(Entity) && dbSetTypes.Contains(t))
                  {
                      //Get all properties of that type
                      var properties = t.GetProperties().ToList();
                      foreach (var p in properties)
                      {
                          //Check if the property has the DefaultValueSqlAttribute
                          var att = p.GetCustomAttribute<DefaultValueSqlAttribute>();
                          if (att != null)
                          {
                              //If any property has the DefaultValueSqlAttribute, set the the value here. Done. 
                              mb.Entity(t).Property(p.Name).HasDefaultValueSql(att.DefaultValueSql);
                          }
                      }
                  }
              }
          }
          
          • 第 5 步 - 在 Visual Studio 的打包程序控制台中启动新的迁移
          Add-Migration MI_000000 -StartupProject Ik.Ws.Login
          
          • 第 6 步 - 查看结果:查看 defaultValueSql = Done
          //This class was entirely created automatic. No manual changes.
          public partial class MI_000000 : Migration
          {
              protected override void Up(MigrationBuilder migrationBuilder)
              {
                  migrationBuilder.CreateTable(
                      name: "tbl_User",
                      columns: table => new
                      {
                          Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "newId()"),
                          EMail = table.Column<string>(type: "nvarchar(max)", nullable: false),
                          Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
                          Forename = table.Column<string>(type: "nvarchar(max)", nullable: true),
                          Street = table.Column<string>(type: "nvarchar(max)", nullable: true),
                          Postalcode = table.Column<string>(type: "nvarchar(max)", nullable: true),
                          MobileNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
                          DateCreated = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()")
                      },
                      constraints: table =>
                      {
                          table.PrimaryKey("PK_tbl_User", x => x.Id);
                      });
              }
          
              protected override void Down(MigrationBuilder migrationBuilder)
              {
                  migrationBuilder.DropTable(
                      name: "tbl_User");
              }
          }
          

          希望对你有所帮助或启发。

          【讨论】:

            【解决方案6】:

            试试这个。此代码默认插入当前日期

            //[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
            public DateTime Created { get; set; } = new DateTime();
            

            【讨论】:

            • 不,它没有。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-06-21
            • 1970-01-01
            • 1970-01-01
            • 2013-07-13
            • 1970-01-01
            相关资源
            最近更新 更多