ChatGPT解决这个技术问题 Extra ChatGPT

Entity Framework 6 Code first Default value

is there "elegant" way to give specific property a default value ?

Maybe by DataAnnotations, something like :

[DefaultValue("true")]
public bool Active { get; set; }

Thank you.

Maybe try in the constructor this.Active = true;? I think the DB value will take precedence when fetching, but careful if new'ing then attaching an entity for an update without a fetch first, as the change tracking might see this as you wanting to update the value. Comment because I haven't used EF in a long time, and I feel like this is a shot in the dark.
Thank you for response, I have used this method so far stackoverflow.com/a/5032578/2913441 but I thought that maybe there is a better way.
public bool Inactive { get; set; } 😉
as Microsoft docs say "You can not set a default value using Data Annotations."

g
gdbdable

You can do it by manually edit code first migration:

public override void Up()
{    
   AddColumn("dbo.Events", "Active", c => c.Boolean(nullable: false, defaultValue: true));
} 

I'm not sure this will work if OP doesn't specially set Active to true when creating an Event object as well. The default value always be false on a non nullable bool property, so unless changed, this is what entity framework will save to the db. Or am I missing something?
@GFoley83, yes, you are right. This method only add default constraint at database level. For complete solution you also need to assign default value in constructor of entity or use property with backed field as shown in answer above
This works for base types. For something like DATETIMEOFFSET, use the , defaultValueSql: "SYSDATETIMEOFFSET", and NOT the defaultValue as this defaultValue: System.DateTimeOffset.Now, will resolve to a string of the current system datetimeoffset value.
AFAIK your manual changes will get lost in case of re-scaffolding the migration
@ninbit i think you shuld write migration to remove it at database level first, then change your DAL mapping
r
ravinsp

It's been a while, but leaving a note for others. I achieved what is needed with an attribute and I decorated my model class fields with that attribute as I want.

[SqlDefaultValue(DefaultValue = "getutcdate()")]
public DateTime CreatedDateUtc { get; set; }

Got the help of these 2 articles:

EF on CodePlex

Andy Mehalick blog

What I did:

Define Attribute

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class SqlDefaultValueAttribute : Attribute
{
    public string DefaultValue { get; set; }
}

In the "OnModelCreating" of the context

modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue));

In the custom SqlGenerator

private void SetAnnotatedColumn(ColumnModel col)
{
    AnnotationValues values;
    if (col.Annotations.TryGetValue("SqlDefaultValue", out values))
    {
         col.DefaultValueSql = (string)values.NewValue;
    }
}

Then in the Migration Configuration constructor, register the custom SQL generator.

SetSqlGenerator("System.Data.SqlClient", new CustomMigrationSqlGenerator());

You can even do it globally without putting [SqlDefaultValue(DefaultValue = "getutcdate()")] on every entity. 1) Simply remove modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue)); 2) Add modelBuilder.Properties().Where(x => x.PropertyType == typeof(DateTime)).Configure(c => c.HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed).HasColumnAnnotation("SqlDefaultValue", "getdate()"));
Where's the custom SqlGenerator please?
Custom SqlGenerator came from here: andy.mehalick.com/2014/02/06/…
@ravinsp why don't customize the MigrationCodeGenerator class to have the migration with the right information rather than the SqlGenerator code? The SQL code is the last step...
@Alex Worth trying! That would also work and would be more elegant than injecting SQL code. But I'm not sure about the complexity of overriding the C# MigrationCodeGenerator.
R
Rudey

The above answers really helped, but only delivered part of the solution. The major issue is that as soon as you remove the Default value attribute, the constraint on the column in database won't be removed. So previous default value will still stay in the database.

Here is a full solution to the problem, including removal of SQL constraints on attribute removal. I am also re-using .NET Framework's native DefaultValue attribute.

Usage

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[DefaultValue("getutcdate()")]
public DateTime CreatedOn { get; set; }

For this to work you need to update IdentityModels.cs and Configuration.cs files

IdentityModels.cs file

Add/update this method in your ApplicationDbContext class

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
            base.OnModelCreating(modelBuilder);
            var convention = new AttributeToColumnAnnotationConvention<DefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.SingleOrDefault().Value.ToString());
            modelBuilder.Conventions.Add(convention);
}

Configuration.cs file

Update your Configuration class constructor by registering custom Sql generator, like this:

internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
    public Configuration()
    {
        // DefaultValue Sql Generator
        SetSqlGenerator("System.Data.SqlClient", new DefaultValueSqlServerMigrationSqlGenerator());
    }
}

Next, add custom Sql generator class (you can add it to the Configuration.cs file or a separate file)

internal class DefaultValueSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    private int dropConstraintCount;

    protected override void Generate(AddColumnOperation addColumnOperation)
    {
        SetAnnotatedColumn(addColumnOperation.Column, addColumnOperation.Table);
        base.Generate(addColumnOperation);
    }

    protected override void Generate(AlterColumnOperation alterColumnOperation)
    {
        SetAnnotatedColumn(alterColumnOperation.Column, alterColumnOperation.Table);
        base.Generate(alterColumnOperation);
    }

    protected override void Generate(CreateTableOperation createTableOperation)
    {
        SetAnnotatedColumns(createTableOperation.Columns, createTableOperation.Name);
        base.Generate(createTableOperation);
    }

    protected override void Generate(AlterTableOperation alterTableOperation)
    {
        SetAnnotatedColumns(alterTableOperation.Columns, alterTableOperation.Name);
        base.Generate(alterTableOperation);
    }

    private void SetAnnotatedColumn(ColumnModel column, string tableName)
    {
        if (column.Annotations.TryGetValue("SqlDefaultValue", out var values))
        {
            if (values.NewValue == null)
            {
                column.DefaultValueSql = null;
                using var writer = Writer();

                // Drop Constraint
                writer.WriteLine(GetSqlDropConstraintQuery(tableName, column.Name));
                Statement(writer);
            }
            else
            {
                column.DefaultValueSql = (string)values.NewValue;
            }
        }
    }

    private void SetAnnotatedColumns(IEnumerable<ColumnModel> columns, string tableName)
    {
        foreach (var column in columns)
        {
            SetAnnotatedColumn(column, tableName);
        }
    }

    private string GetSqlDropConstraintQuery(string tableName, string columnName)
    {
        var tableNameSplitByDot = tableName.Split('.');
        var tableSchema = tableNameSplitByDot[0];
        var tablePureName = tableNameSplitByDot[1];

        var str = $@"DECLARE @var{dropConstraintCount} nvarchar(128)
SELECT @var{dropConstraintCount} = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'{tableSchema}.[{tablePureName}]')
AND col_name(parent_object_id, parent_column_id) = '{columnName}';
IF @var{dropConstraintCount} IS NOT NULL
EXECUTE('ALTER TABLE {tableSchema}.[{tablePureName}] DROP CONSTRAINT [' + @var{dropConstraintCount} + ']')";

        dropConstraintCount++;
        return str;
    }
}

This approach worked perfectly for me. One enhancement I made was to also override Generate(CreateTableOperation createTableOperation) and Generate(AddColumnOperation addColumnOperation) with the same logic so those scenarios are also caught. I also only check values.NewValue is null as I wanted my default to be an empty string.
@Delorian I have updated my answer, thank you for your comments
I've made an edit to your post to support instances where a rollback is dropping more than one constraint. Your script would throw an error stating that @con was already declared. I created a private variable to hold a counter and simply increment it. I also changed the format of the drop constraint to more closely match what EF sends to SQL when creating the constraint. Great work on this!
Thanks for solution but I have two problems: 1. Table names need brackets. 2. In update new value not set and default value set instead!
Do I need the set the [DatabaseGenerated(DatabaseGeneratedOption.Computed)] attribute? If so, why? In my tests it seemed to have no effect leaving it out.
C
Community

Your model properties don't have to be 'auto properties' Even though that is easier. And the DefaultValue attribute is really only informative metadata The answer accepted here is one alternative to the constructor approach.

public class Track
{

    private const int DEFAULT_LENGTH = 400;
    private int _length = DEFAULT_LENGTH;
    [DefaultValue(DEFAULT_LENGTH)]
    public int LengthInMeters {
        get { return _length; }
        set { _length = value; }
    }
}

vs.

public class Track
{
    public Track()
    {
        LengthInMeters = 400;   
    }

    public int LengthInMeters { get; set; }        
}

This will only work for applications creating and consuming data using this specific class. Usually this isn't a problem if data access code is centralized. To update the value across all applications you need to configure the datasource to set a default value. Devi's answer shows how it can be done using migrations, sql, or whatever language your data source speaks.


Note: This will not set a default value in the database. Other programs not using your entity will not get that default value.
This part of the answer, but it doesn't work if you are inserting records in ways other than through Entity Framework. Also if you are creating a new non-null column on a table, this will not give you the ability to set the default value for existing records. @devi has a valuable addition below.
Why is your first approach the "correct" way? Will you run into unintended issues with the constructor approach?
a matter of opinion, and those change :) And probably not.
Under certain circumstances, I've had issues with the constructor approach; setting a default value in the backing field seems to be the least invasive solution.
S
Sameh Deabes

What I did, I initialized values in the constructor of the entity

Note: DefaultValue attributes won't set the values of your properties automatically, you have to do it yourself


The problem with the constructor setting the value is that EF will do an update to the database when committing the transaction.
The model first crete the default values by this way
The DefaultValue is a species that lives in far away, foreign lands, too shy to encounter your properties by itself. Do not make a sound, it is easily scared away - should it ever come near enough to hear it. +1 for stating the not at all obvious.
A
Anthony Griggs

I admit that my approach escapes the whole "Code First" concept. But if you have the ability to just change the default value in the table itself... it's much simpler than the lengths that you have to go through above... I'm just too lazy to do all that work!

It almost seems as if the posters original idea would work:

[DefaultValue(true)]
public bool IsAdmin { get; set; }

https://i.stack.imgur.com/GGJ0g.png


D
Denny Puig

After @SedatKapanoglu comment, I am adding all my approach that works, because he was right, just using the fluent API does not work.

1- Create custom code generator and override Generate for a ColumnModel.

   public class ExtendedMigrationCodeGenerator : CSharpMigrationCodeGenerator
{

    protected override void Generate(ColumnModel column, IndentedTextWriter writer, bool emitName = false)
    {

        if (column.Annotations.Keys.Contains("Default"))
        {
            var value = Convert.ChangeType(column.Annotations["Default"].NewValue, column.ClrDefaultValue.GetType());
            column.DefaultValue = value;
        }


        base.Generate(column, writer, emitName);
    }

}

2- Assign the new code generator:

public sealed class Configuration : DbMigrationsConfiguration<Data.Context.EfSqlDbContext>
{
    public Configuration()
    {
        CodeGenerator = new ExtendedMigrationCodeGenerator();
        AutomaticMigrationsEnabled = false;
    }
}

3- Use fluent api to created the Annotation:

public static void Configure(DbModelBuilder builder){    
builder.Entity<Company>().Property(c => c.Status).HasColumnAnnotation("Default", 0);            
}

Please could see my complete solution, I added all my implementation of how it works, thanks.
R
Roar Jørstad

In .NET Core 3.1 you can do the following in the model class:

    public bool? Active { get; set; } 

In the DbContext OnModelCreating you add the default value.

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Foundation>()
            .Property(b => b.Active)
            .HasDefaultValueSql("1");

        base.OnModelCreating(modelBuilder);
    }

Resulting in the following in the database

https://i.stack.imgur.com/rctQH.png

Note: If you don't have nullable (bool?) for you property you will get the following warning

The 'bool' property 'Active' on entity type 'Foundation' is configured with a database-generated default. This default will always be used for inserts when the property has the value 'false', since this is the CLR default for the 'bool' type. Consider using the nullable 'bool?' type instead so that the default will only be used for inserts when the property value is 'null'.

OP's question is regarding EF6, not EF Core.
M
Marisol Gutiérrez

It's simple! Just annotate with required.

[Required]
public bool MyField { get; set; }

the resultant migration will be:

migrationBuilder.AddColumn<bool>(
name: "MyField",
table: "MyTable",
nullable: false,
defaultValue: false);

If you want true, change the defaultValue to true in the migration before updating the database


the autogenerated migration can then be changed and you will forguet about the defaultvalue
Simple and works, helps to quickly add a column to an existing table with a foreign key constraint on the new column. Thanks
And what if we need the default value to be true?
V
Velyo

I found that just using Auto-Property Initializer on entity property is enough to get the job done.

For example:

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

You'd think this would work, with code first. But it did not for me with EF 6.2.
This actually works perfectly for Code First EF 6. I am not sure why negative score for this answer. I gave one upvote.
Does not work with EF Core 5.0.13. I ended up just manually editing the migration to have ..., defaultValue: false);
R
Ruli
using System.ComponentModel;

[DefaultValue(true)]

public bool Active { get; set; }

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
S
Sracanis

In EF core released 27th June 2016 you can use fluent API for setting default value. Go to ApplicationDbContext class, find/create the method name OnModelCreating and add the following fluent API.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<YourTableName>()
        .Property(b => b.Active)
        .HasDefaultValue(true);
}

OP asks about EF6, not EF Core
If someone is interested. I've found a way to use the [DefaultValue] attribute (described in another answer here) in EF Core. stackoverflow.com/a/64803061/1462234
B
Bappa Malakar

Just Overload the default constructor of Model class and pass any relevant parameter which you may or may not use. By this you can easily supply default values for attributes. Below is an example.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Aim.Data.Domain
{
    [MetadataType(typeof(LoginModel))]
    public partial class Login
    {       
        public Login(bool status)
        {
            this.CreatedDate = DateTime.Now;
            this.ModifiedDate = DateTime.Now;
            this.Culture = "EN-US";
            this.IsDefaultPassword = status;
            this.IsActive = status;
            this.LoginLogs = new HashSet<LoginLog>();
            this.LoginLogHistories = new HashSet<LoginLogHistory>();
        }


    }

    public class LoginModel
    {

        [Key]
        [ScaffoldColumn(false)] 
        public int Id { get; set; }
        [Required]
        public string LoginCode { get; set; }
        [Required]
        public string Password { get; set; }
        public string LastPassword { get; set; }     
        public int UserGroupId { get; set; }
        public int FalseAttempt { get; set; }
        public bool IsLocked { get; set; }
        public int CreatedBy { get; set; }       
        public System.DateTime CreatedDate { get; set; }
        public Nullable<int> ModifiedBy { get; set; }      
        public Nullable<System.DateTime> ModifiedDate { get; set; }       
        public string Culture { get; set; }        
        public virtual ICollection<LoginLog> LoginLogs { get; set; }
        public virtual ICollection<LoginLogHistory> LoginLogHistories { get; set; }
    }

}

This suggestion is totally client side logic. This "works" as long as you will only interact with the database using the application. As soon as someone wants to insert a record manually or from another application, you realize the downside that there is no default expression in the schema and this cannot scale to other clients. Although it wasn't explicitly stated, this legitimate topic implies the requirement to get EF to create a migration that places a default expression into the column definition.
N
Nader Gharibian Fard

The Entity Framework Core Fluent API HasDefaultValue method is used to specify the default value for a database column mapped to a property. The value must be a constant.

public class Contact
{
    public int ContactId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public bool IsActive { get; set; }
    public DateTime DateCreated { get; set; }
}
public clas SampleContext : DbContext
{
    public DbSet<Contact> Contacts { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Context>()
            .Propery(p => p.IsActive)
            .HasDefaultValue(true);
    }
}

Or

like it!

You can also specify a SQL fragment that is used to calculate the default value:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Created)
        .HasDefaultValueSql("getdate()");
}

Duplicates this answer.
H
Hatef.

Lets consider you have a class name named Products and you have a IsActive field. just you need a create constructor :

Public class Products
{
    public Products()
    {
       IsActive = true;
    }
 public string Field1 { get; set; }
 public string Field2 { get; set; }
 public bool IsActive { get; set; }
}

Then your IsActive default value is True!

Edite :

if you want to do this with SQL use this command :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.IsActive)
        .HasDefaultValueSql("true");
}

Simply this wasn't the question. It was about default value constraints in the database itself.
@Hatef, your edit only applies to EF Core. The question is about EF 6.
This HasDefaultValueSql is not available in EF6
A
Acorndog

Hmm... I do DB first, and in that case, this is actually a lot easier. EF6 right? Just open your model, right click on the column you want to set a default for, choose properties, and you will see a "DefaultValue" field. Just fill that out and save. It will set up the code for you.

Your mileage may vary on code first though, I haven't worked with that.

The problem with a lot of other solutions, is that while they may work initially, as soon as you rebuild the model, it will throw out any custom code you inserted into the machine-generated file.

This method works by adding an extra property to the edmx file:

<EntityType Name="Thingy">
  <Property Name="Iteration" Type="Int32" Nullable="false" **DefaultValue="1"** />

And by adding the necessary code to the constructor:

public Thingy()
{
  this.Iteration = 1;

F
Fabien

Set the default value for the column in table in MSSQL Server, and in class code add attribute, like this:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

for the same property.