Entity Framework 6 Code first Nilai default


204

Adakah cara "elegan" untuk memberikan properti default nilai default?

Mungkin oleh DataAnnotations, sesuatu seperti:

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

Terima kasih.


Mungkin coba di konstruktor this.Active = true;? Saya pikir nilai DB akan diutamakan saat mengambil, tapi hati-hati jika baru kemudian lampirkan entitas untuk pembaruan tanpa mengambil pertama, karena pelacakan perubahan mungkin melihat ini ketika Anda ingin memperbarui nilai. Komentar karena saya belum pernah menggunakan EF dalam waktu yang lama, dan saya merasa ini adalah kesempatan yang sulit.
AaronLS

3
Terima kasih atas tanggapannya, saya telah menggunakan metode ini sejauh ini stackoverflow.com/a/5032578/2913441 tapi saya pikir mungkin ada cara yang lebih baik.
marino-krk

2
public bool Inactive { get; set; }😉
Jesse Hufstetler

seperti yang dikatakan Microsoft docs "Anda tidak dapat menetapkan nilai default menggunakan Anotasi Data."
hmfarimani

Silakan
merujuk pada

Jawaban:


167

Anda dapat melakukannya dengan mengedit kode migrasi terlebih dahulu secara manual:

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

6
Saya tidak yakin ini akan bekerja jika OP tidak khusus diatur Activeuntuk truesaat membuat Eventobjek juga. Nilai default selalu berada falsepada properti bool yang tidak dapat dibatalkan, jadi kecuali diubah, ini adalah kerangka kerja entitas yang akan disimpan ke db. Atau apakah saya melewatkan sesuatu?
GFoley83

6
@ GFoley83, ya, Anda benar. Metode ini hanya menambah batasan default di tingkat basis data. Untuk solusi lengkap, Anda juga perlu menetapkan nilai default dalam konstruktor entitas atau menggunakan properti dengan bidang yang didukung seperti yang ditunjukkan dalam jawaban di atas
gdbdable

1
Ini berfungsi untuk tipe dasar. Untuk sesuatu seperti DATETIMEOFFSET, gunakan, defaultValueSql: "SYSDATETIMEOFFSET", dan BUKAN defaultValue sebagai defaultValue ini: System.DateTimeOffset.Now, akan menyelesaikan serangkaian string nilai sistem saat ini untuk mengatur nilai saat ini.
OzBob

3
AFAIK perubahan manual Anda akan hilang jika perancah kembali migrasi
Alexander Powolozki

1
@ninbit saya pikir Anda harus menulis migrasi untuk menghapusnya di tingkat basis data terlebih dahulu, lalu ubah pemetaan DAL Anda
gdbdable

74

Sudah lama, tetapi meninggalkan catatan untuk orang lain. Saya mencapai apa yang dibutuhkan dengan atribut dan saya menghiasi bidang kelas model saya dengan atribut yang saya inginkan.

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

Mendapat bantuan dari 2 artikel ini:

Apa yang saya lakukan:

Tentukan Atribut

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

Dalam "OnModelCreating" dari konteksnya

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

Di SqlGenerator kustom

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

Kemudian di konstruktor Konfigurasi Migrasi, daftarkan generator SQL khusus.

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

6
Anda bahkan dapat melakukannya secara global tanpa mengenakan [SqlDefaultValue(DefaultValue = "getutcdate()")]setiap entitas. 1) Cukup hapus modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue)); 2) TambahmodelBuilder.Properties().Where(x => x.PropertyType == typeof(DateTime)).Configure(c => c.HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed).HasColumnAnnotation("SqlDefaultValue", "getdate()"));
Daniel Skowroński

1
Di mana SqlGenerator khusus?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ

3
SqlGenerator khusus datang dari sini: andy.mehalick.com/2014/02/06/…
ravinsp

1
@ravinsp mengapa tidak mengkustomisasi kelas MigrationCodeGenerator untuk melakukan migrasi dengan informasi yang benar daripada kode SqlGenerator? Kode SQL adalah langkah terakhir ...
Alex

@ Alex Layak dicoba! Itu juga akan bekerja dan akan lebih elegan daripada menyuntikkan kode SQL. Tapi saya tidak yakin tentang kerumitan mengganti C # MigrationCodeGenerator.
ravinsp

73

Jawaban di atas sangat membantu, tetapi hanya memberikan sebagian dari solusi. Masalah utama adalah bahwa segera setelah Anda menghapus atribut nilai Default, batasan pada kolom dalam database tidak akan dihapus. Jadi nilai default sebelumnya akan tetap berada di database.

Berikut ini adalah solusi lengkap untuk masalah ini, termasuk penghapusan kendala SQL pada penghapusan atribut. Saya juga menggunakan kembali DefaultValueatribut asli .NET Framework .

Pemakaian

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

Agar ini berfungsi, Anda perlu memperbarui file IdentityModels.cs dan Configuration.cs

File IdentityModels.cs

Tambahkan / perbarui metode ini di ApplicationDbContextkelas Anda

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);
}

File Configuration.cs

Perbarui Configurationkonstruktor kelas Anda dengan mendaftarkan generator Sql khusus, seperti ini:

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

Selanjutnya, tambahkan kelas generator Sql khusus (Anda dapat menambahkannya ke file Configuration.cs atau file terpisah)

internal class DefaultValueSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    private int dropConstraintCount = 0;

    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)
    {
        AnnotationValues values;
        if (column.Annotations.TryGetValue("SqlDefaultValue", out 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 tableNameSplittedByDot = tableName.Split('.');
        var tableSchema = tableNameSplittedByDot[0];
        var tablePureName = tableNameSplittedByDot[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 = dropConstraintCount + 1;
        return str;
    }
}

2
Pendekatan ini sangat cocok untuk saya. Salah satu peningkatan yang saya buat adalah juga mengesampingkan Generate (CreateTableOperation createTableOperation) dan Generate (AddColumnOperation addColumnOperation) dengan logika yang sama sehingga skenario tersebut juga tertangkap. Saya juga hanya memeriksa values.NewValue adalah null karena saya ingin default saya menjadi string kosong.
Delorian

2
@Delorian Saya telah memperbarui jawaban saya, terima kasih atas komentar Anda
Jevgenij Martynenko

1
Saya telah mengedit posting Anda untuk mendukung contoh di mana rollback menjatuhkan lebih dari satu kendala. Skrip Anda akan menampilkan kesalahan yang menyatakan bahwa @con sudah dideklarasikan. Saya membuat variabel pribadi untuk menyimpan penghitung dan hanya menambahnya. Saya juga mengubah format batasan jatuh untuk lebih mencocokkan dengan apa yang EF kirim ke SQL saat membuat batasan. Kerja bagus untuk ini!
Brad

1
Terima kasih atas solusinya tetapi saya memiliki dua masalah: 1. Nama tabel memerlukan tanda kurung. 2. Dalam pembaruan, nilai baru tidak disetel dan nilai default ditetapkan sebagai gantinya!
Omid-RH

1
Apakah saya perlu mengatur [DatabaseGenerated(DatabaseGeneratedOption.Computed)]atribut? Jika demikian, mengapa? Dalam tes saya sepertinya tidak berpengaruh meninggalkannya.
RamNow

26

Properti model Anda tidak harus menjadi 'properti otomatis' Meskipun itu lebih mudah. Dan atribut DefaultValue benar-benar hanya metadata informatif. Jawaban yang diterima di sini adalah salah satu alternatif dari pendekatan konstruktor.

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; }        
}

Ini hanya akan berfungsi untuk aplikasi yang membuat dan menggunakan data menggunakan kelas khusus ini. Biasanya ini bukan masalah jika kode akses data terpusat. Untuk memperbarui nilai di semua aplikasi, Anda perlu mengkonfigurasi sumber data untuk menetapkan nilai default. Jawaban Devi menunjukkan bagaimana hal itu dapat dilakukan dengan menggunakan migrasi, sql, atau bahasa apa pun yang digunakan oleh sumber data Anda.


21
Catatan: Ini tidak akan menetapkan nilai default dalam database . Program lain yang tidak menggunakan entitas Anda tidak akan mendapatkan nilai default itu.
Eric J.

Ini bagian dari jawabannya, tetapi tidak berfungsi jika Anda menyisipkan rekaman dengan cara lain selain melalui Entity Framework. Juga jika Anda membuat kolom bukan-nol baru pada tabel, ini tidak akan memberi Anda kemampuan untuk menetapkan nilai default untuk catatan yang ada. @devi memiliki tambahan yang berharga di bawah ini.
d512

Mengapa pendekatan pertama Anda dengan cara yang "benar"? Apakah Anda akan mengalami masalah yang tidak diinginkan dengan pendekatan konstruktor?
WiteCastle

masalah pendapat, dan perubahan itu :) Dan mungkin tidak.
calebboyd

2
Dalam keadaan tertentu, saya memiliki masalah dengan pendekatan konstruktor; menetapkan nilai default di bidang dukungan tampaknya menjadi solusi yang paling tidak invasif.
Lucent Fox

11

Apa yang saya lakukan, saya menginisialisasi nilai dalam konstruktor entitas

Catatan: Atribut DefaultValue tidak akan menetapkan nilai properti Anda secara otomatis, Anda harus melakukannya sendiri


4
Masalah dengan konstruktor yang menetapkan nilainya adalah bahwa EF akan melakukan pembaruan ke basis data ketika melakukan transaksi.
Luka

Model ini pertama-tama menuliskan nilai default dengan cara ini
Lucas

DefaultValue adalah spesies yang hidup di tempat yang jauh, tanah asing, terlalu malu untuk menjumpai properti Anda sendiri. Jangan bersuara, mudah ditakuti - seandainya itu cukup dekat untuk mendengarnya. +1 untuk menyatakan yang sama sekali tidak jelas.
Risadinha

8

Setelah komentar @SedatKapanoglu, saya menambahkan semua pendekatan saya yang berfungsi, karena dia benar, hanya menggunakan API yang lancar tidak bekerja.

1- Buat pembuat kode khusus dan ganti Generate untuk 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- Tetapkan pembuat kode baru:

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

3- Gunakan api lancar untuk membuat Anotasi:

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

Harap dapat melihat solusi lengkap saya, saya menambahkan semua implementasi saya tentang cara kerjanya, terima kasih.
Denny Puig

5

Itu mudah! Hanya perlu penjelasan.

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

migrasi yang dihasilkan adalah:

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

Jika Anda ingin true, ubah DefaultValue menjadi true dalam migrasi sebelum memperbarui database


migrasi autogenerated kemudian dapat diubah dan Anda akan lupa tentang nilai default
Fernando Torres

Sederhana dan berfungsi, membantu untuk dengan cepat menambahkan kolom ke tabel yang ada dengan batasan kunci asing pada kolom baru. Terima kasih
po10cySA

Dan bagaimana jika kita membutuhkan nilai default true?
Christos Lytras

5

Saya akui bahwa pendekatan saya lolos dari keseluruhan konsep "Code First". Tetapi jika Anda memiliki kemampuan untuk hanya mengubah nilai default di tabel itu sendiri ... itu jauh lebih sederhana daripada panjang yang harus Anda lalui di atas ... Saya terlalu malas untuk melakukan semua itu bekerja!

Tampaknya seolah-olah ide-ide asli poster akan berhasil:

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

Saya pikir mereka hanya membuat kesalahan dengan menambahkan tanda kutip ... tapi sayangnya tidak ada intuisi seperti itu. Saran lain terlalu banyak bagi saya (mengingat saya memiliki hak istimewa yang diperlukan untuk masuk ke meja dan membuat perubahan ... di mana tidak setiap pengembang akan dalam setiap situasi). Pada akhirnya saya hanya melakukannya dengan cara kuno. Saya menetapkan nilai default dalam tabel SQL Server ... Maksud saya benar-benar, sudah cukup! CATATAN: Saya selanjutnya diuji melakukan add-migrasi dan pembaruan-database dan perubahan macet. masukkan deskripsi gambar di sini


1

Hanya Membebani konstruktor default kelas Model dan melewati parameter terkait yang mungkin Anda gunakan atau tidak. Dengan ini, Anda dapat dengan mudah memberikan nilai default untuk atribut. Di bawah ini adalah contohnya.

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; }
    }

}

Saran ini benar-benar logika sisi klien. Ini "berfungsi" selama Anda hanya akan berinteraksi dengan database menggunakan aplikasi. Segera setelah seseorang ingin menyisipkan catatan secara manual atau dari aplikasi lain, Anda menyadari kelemahannya bahwa tidak ada ekspresi default dalam skema dan ini tidak dapat menskala ke klien lain. Meskipun tidak secara eksplisit dinyatakan, topik yang sah ini menyiratkan persyaratan untuk membuat EF untuk membuat migrasi yang menempatkan ekspresi default ke dalam definisi kolom.
Tom

0

Mari kita anggap Anda memiliki nama kelas bernama Produk dan Anda memiliki bidang IsActive. Anda hanya perlu membuat konstruktor:

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

Maka nilai default IsActive Anda adalah Benar!

Edite :

jika Anda ingin melakukan ini dengan SQL, gunakan perintah ini:

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

Cukup ini bukan pertanyaannya. Itu tentang batasan nilai default dalam database itu sendiri.
Gábor

4
@Hatef, hasil edit Anda hanya berlaku untuk EF Core. Pertanyaannya adalah tentang EF 6.
Michael

3
HasDefaultValueSql ini tidak tersedia di EF6
tatigo

0

Di EF core yang dirilis 27 Juni 2016 Anda dapat menggunakan API yang lancar untuk menetapkan nilai default. Pergi ke kelas ApplicationDbContext, cari / buat nama metode OnModelCreating dan tambahkan API lancar berikut.

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

0

Di .NET Core 3.1 Anda dapat melakukan hal berikut di kelas model:

    public bool? Active { get; set; } 

Di DbContext OnModelCreating Anda menambahkan nilai default.

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

        base.OnModelCreating(modelBuilder);
    }

Menghasilkan berikut dalam database

masukkan deskripsi gambar di sini

Catatan: Jika Anda tidak memiliki properti nullable (bool?) Untuk Anda, Anda akan mendapatkan peringatan berikut

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'.

-3

Saya menemukan bahwa hanya menggunakan Auto-Property Initializer pada properti entitas sudah cukup untuk menyelesaikan pekerjaan.

Sebagai contoh:

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

2
Anda akan berpikir ini akan berhasil, dengan kode terlebih dahulu. Tapi itu bukan untuk saya dengan EF 6.2.
user1040323

-4

Hmm ... Saya melakukan DB terlebih dahulu, dan dalam hal ini, ini sebenarnya jauh lebih mudah. EF6 bukan? Cukup buka model Anda, klik kanan pada kolom yang ingin Anda tetapkan default, pilih properti, dan Anda akan melihat bidang "DefaultValue". Cukup isi dan simpan. Ini akan mengatur kode untuk Anda.

Jarak tempuh Anda mungkin bervariasi pada kode terlebih dahulu, saya belum bekerja dengan itu.

Masalah dengan banyak solusi lain, adalah bahwa meskipun mereka dapat bekerja pada awalnya, segera setelah Anda membangun kembali model, itu akan membuang kode kustom apa pun yang Anda masukkan ke file yang dihasilkan mesin.

Metode ini berfungsi dengan menambahkan properti tambahan ke file edmx:

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

Dan dengan menambahkan kode yang diperlukan ke konstruktor:

public Thingy()
{
  this.Iteration = 1;

-5

Tetapkan nilai default untuk kolom dalam tabel di MSSQL Server, dan dalam atribut class code add, seperti ini:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

untuk properti yang sama.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.