Kode EF Kunci asing pertama tanpa properti navigasi

91

Katakanlah saya memiliki entitas berikut:

public class Parent
{
    public int Id { get; set; }
}
public class Child
{
    public int Id { get; set; }
    public int ParentId { get; set; }
}

Apa kode sintaks API pertama yang fasih untuk menegakkan ParentId yang dibuat dalam database dengan batasan kunci asing ke tabel Parents, tanpa perlu memiliki properti navigasi ?

Saya tahu bahwa jika saya menambahkan properti navigasi Parent to Child, maka saya bisa melakukan ini:

modelBuilder.Entity<Child>()
    .HasRequired<Parent>(c => c.Parent)
    .WithMany()
    .HasForeignKey(c => c.ParentId);

Tetapi saya tidak ingin properti navigasi dalam kasus khusus ini.

RationalGeek
sumber
1
Saya rasa ini sebenarnya tidak mungkin hanya dengan EF, Anda mungkin perlu menggunakan beberapa SQL mentah dalam migrasi manual untuk menyiapkannya
Not loved
@LukeMcGregor itulah yang saya takuti. Jika Anda membuat jawaban itu, saya akan dengan senang hati menerimanya, dengan asumsi itu benar. :-)
RationalGeek
Apakah ada alasan khusus untuk tidak memiliki properti navigasi? Akan membuat properti navigasi menjadi pribadi berfungsi untuk Anda - tidak akan terlihat di luar entitas tetapi akan menyenangkan EF. (Catatan saya belum mencoba ini tetapi saya pikir ini seharusnya berfungsi - lihat posting ini tentang pemetaan properti pribadi romiller.com/2012/10/01/… )
Pawel
6
Saya tidak menginginkannya karena saya tidak membutuhkannya. Saya tidak suka harus memasukkan barang ekstra yang tidak perlu dalam desain hanya untuk memenuhi persyaratan kerangka kerja. Apakah akan membunuh saya jika memasang alat bantu navigasi? Tidak. Sebenarnya itulah yang saya lakukan untuk saat ini.
RationalGeek
Anda selalu membutuhkan properti navigasi setidaknya di satu sisi untuk membangun relasi. Untuk informasi lebih lanjut stackoverflow.com/a/7105288/105445
Wahid Bitar

Jawaban:

63

Dengan API EF Code First Fluent tidak mungkin. Anda selalu membutuhkan setidaknya satu properti navigasi untuk membuat batasan kunci asing dalam database.

Jika Anda menggunakan Migrasi Pertama Kode, Anda memiliki opsi untuk menambahkan migrasi berbasis kode baru di konsol pengelola paket ( add-migration SomeNewSchemaName). Jika Anda mengubah sesuatu dengan model atau pemetaan, migrasi baru akan ditambahkan. Jika Anda tidak mengubah apa pun, paksa migrasi baru dengan menggunakan add-migration -IgnoreChanges SomeNewSchemaName. Migrasi hanya akan berisi metode Updan kosong Downdalam kasus ini.

Kemudian Anda dapat memodifikasi Upmetode dengan menambahkan follwing ke dalamnya:

public override void Up()
{
    // other stuff...

    AddForeignKey("ChildTableName", "ParentId", "ParentTableName", "Id",
        cascadeDelete: true); // or false
    CreateIndex("ChildTableName", "ParentId"); // if you want an index
}

Menjalankan migrasi ini ( update-databasedi konsol pengelolaan paket) akan menjalankan pernyataan SQL yang mirip dengan ini (untuk SQL Server):

ALTER TABLE [ChildTableName] ADD CONSTRAINT [FK_SomeName]
FOREIGN KEY ([ParentId]) REFERENCES [ParentTableName] ([Id])

CREATE INDEX [IX_SomeName] ON [ChildTableName] ([ParentId])

Atau, tanpa migrasi, Anda bisa menjalankan perintah SQL murni menggunakan

context.Database.ExecuteSqlCommand(sql);

di mana contextadalah instance dari kelas konteks turunan Anda dan sqlhanya perintah SQL di atas sebagai string.

Sadarilah bahwa dengan semua ini EF tidak memiliki petunjuk yang ParentIdmerupakan kunci asing yang menggambarkan suatu hubungan. EF akan menganggapnya hanya sebagai properti skalar biasa. Entah bagaimana semua hal di atas hanyalah cara yang lebih rumit dan lebih lambat dibandingkan dengan hanya membuka alat manajemen SQL dan menambahkan batasan dengan tangan.

Slauma
sumber
2
Penyederhanaan berasal dari otomatisasi: Saya tidak memiliki akses ke lingkungan lain tempat kode saya diterapkan. Mampu melakukan perubahan kode ini bagus untuk saya. Tapi saya suka snark :)
pomeroy
Saya rasa Anda juga baru saja menetapkan atribut pada entitas, jadi pada id induk, tambahkan saja [ForeignKey("ParentTableName")]. Itu akan menghubungkan properti ke kunci apa pun yang ada di tabel induk. Sekarang Anda memiliki nama tabel hard-code.
Triynko
2
Ini jelas bukan tidak mungkin, lihat komentar lain di bawah Mengapa ini bahkan ditandai sebagai jawaban yang benar
Igor Be
112

Meskipun posting ini Entity Frameworkbukan untuk Entity Framework Core, Mungkin berguna bagi seseorang yang ingin mencapai hal yang sama menggunakan Entity Framework Core (Saya menggunakan V1.1.2).

Saya tidak perlu properti navigasi (meskipun mereka baik) karena saya berlatih DDD dan saya ingin Parentdan Childmenjadi dua akar agregat terpisah. Saya ingin mereka dapat berbicara satu sama lain melalui kunci asing, bukan melalui Entity Frameworkproperti navigasi khusus infrastruktur .

Yang harus Anda lakukan adalah mengonfigurasi hubungan di satu sisi menggunakan HasOnedan WithManytanpa menentukan properti navigasi (sama sekali tidak ada).

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}

    protected override void OnModelCreating(ModelBuilder builder)
    {
        ......

        builder.Entity<Parent>(b => {
            b.HasKey(p => p.Id);
            b.ToTable("Parent");
        });

        builder.Entity<Child>(b => {
            b.HasKey(c => c.Id);
            b.Property(c => c.ParentId).IsRequired();

            // Without referencing navigation properties (they're not there anyway)
            b.HasOne<Parent>()    // <---
                .WithMany()       // <---
                .HasForeignKey(c => c.ParentId);

            // Just for comparison, with navigation properties defined,
            // (let's say you call it Parent in the Child class and Children
            // collection in Parent class), you might have to configure them 
            // like:
            // b.HasOne(c => c.Parent)
            //     .WithMany(p => p.Children)
            //     .HasForeignKey(c => c.ParentId);

            b.ToTable("Child");
        });

        ......
    }
}

Saya memberikan contoh tentang cara mengonfigurasi properti entitas juga, tetapi yang paling penting di sini adalah HasOne<>, WithMany()dan HasForeignKey().

Semoga membantu.

David Liang
sumber
9
Ini adalah jawaban yang benar untuk EF Core, dan bagi mereka yang mempraktikkan DDD, ini HARUS.
Thiago Silva
1
Saya tidak jelas tentang apa yang telah berubah untuk menghapus properti navigasi. Bisakah Anda menjelaskan?
andrew.rockwell
3
@ andrew.rockwell: Lihat HasOne<Parent>()dan .WithMany()di konfigurasi anak mereka. Mereka tidak mereferensikan properti navigasi sama sekali, karena tidak ada properti navigasi yang ditentukan. Saya akan mencoba membuatnya lebih jelas dengan pembaruan saya.
David Liang
Hebat. Terima kasih @dvvvn
andrian.rockwell
2
@ mirind4 tidak terkait dengan sampel kode tertentu di OP, jika Anda memetakan entitas akar agregat yang berbeda ke tabel DB masing-masing, menurut DDD, entri akar tersebut hanya boleh mereferensikan satu sama lain melalui identitasnya, dan tidak boleh berisi referensi lengkap ke entitas AR lainnya. Faktanya, di DDD itu juga umum untuk menjadikan identitas entitas sebagai objek nilai daripada menggunakan alat peraga dengan tipe primitif seperti int / log / guid (terutama entitas AR), menghindari obsesi primitif dan juga memungkinkan AR yang berbeda untuk mereferensikan entitas melalui nilai tipe ID objek. HTH
Thiago Silva
21

Petunjuk kecil bagi mereka, yang ingin menggunakan DataAnotations dan tidak ingin mengekspos Properti Navigasi - gunakan protected

public class Parent
{
    public int Id { get; set; }
}
public class Child
{
    public int Id { get; set; }
    public int ParentId { get; set; }

    protected virtual Parent Parent { get; set; }
}

Itu saja - kunci asing dengan cascade:truesetelah Add-Migrationakan dibuat.

tenbits
sumber
1
Bahkan bisa secara pribadi
Marc Wittke
2
Saat membuat Anak, apakah Anda harus menugaskan Parentatau hanya ParentId?
George Mauer
5
virtualProperti @arcWke tidak boleh private.
LINQ
@GeorgeMauer: itu pertanyaan yang bagus! Secara teknis salah satu akan berfungsi, tetapi juga menjadi masalah ketika Anda memiliki kode yang tidak konsisten seperti ini, karena pengembang (terutama pendatang baru) tidak yakin apa yang harus diteruskan.
David Liang
14

Dalam kasus EF Core Anda tidak perlu menyediakan properti navigasi. Anda cukup memberikan Kunci Asing di satu sisi hubungan. Contoh sederhana dengan Fluent API:

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;

namespace EFModeling.Configuring.FluentAPI.Samples.Relationships.NoNavigation
{
    class MyContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
             modelBuilder.Entity<Post>()
                .HasOne<Blog>()
                .WithMany()
                .HasForeignKey(p => p.BlogId);
        }
    }

    public class Blog
    {
         public int BlogId { get; set; }
         public string Url { get; set; }
    }

    public class Post
    {
         public int PostId { get; set; }
         public string Title { get; set; }
         public string Content { get; set; }

        public int BlogId { get; set; }
    }
}
Jonatan Dragon
sumber
2

Saya menggunakan .Net Core 3.1, EntityFramework 3.1.3. Saya telah mencari-cari dan Solusi yang saya temukan menggunakan versi generik HasForeginKey<DependantEntityType>(e => e.ForeginKeyProperty). Anda dapat membuat relasi satu ke satu seperti ini:

builder.entity<Parent>()
.HasOne<Child>()
.WithOne<>()
.HasForeginKey<Child>(c => c.ParentId);

builder.entity<Child>()
    .Property(c => c.ParentId).IsRequired();

Semoga ini bisa membantu atau setidaknya memberikan beberapa ide lain tentang cara menggunakan HasForeginKeymetode ini.

Radded Weaver
sumber
0

Alasan saya tidak menggunakan properti navigasi adalah ketergantungan kelas. Saya memisahkan model saya menjadi beberapa rakitan, yang dapat digunakan atau tidak digunakan dalam proyek yang berbeda dalam kombinasi apa pun. Jadi jika saya memiliki entitas yang memiliki properti nagivation ke kelas dari rakitan lain, saya perlu merujuk rakitan itu, yang ingin saya hindari (atau proyek apa pun yang menggunakan bagian dari model data lengkap itu akan membawa semuanya).

Dan saya memiliki aplikasi migrasi terpisah, yang digunakan untuk migrasi (saya menggunakan otomatisasi) dan pembuatan DB awal. Proyek ini mereferensikan semuanya dengan alasan yang jelas.

Solusinya adalah gaya-C:

  • "salin" file dengan kelas target ke proyek migrasi melalui tautan (seret-n-lepas dengan altkunci di VS)
  • nonaktifkan properti nagivation (dan atribut FK) melalui #if _MIGRATION
  • setel definisi preprocessor di aplikasi migrasi dan jangan setel di project model, sehingga tidak akan mereferensikan apa pun (jangan mereferensikan assembly dengan Contactclass sebagai contoh).

Sampel:

    public int? ContactId { get; set; }

#if _MIGRATION
    [ForeignKey(nameof(ContactId))]
    public Contact Contact { get; set; }
#endif

Tentu saja Anda harus cara yang sama menonaktifkan usingdirektif dan mengubah namespace.

Setelah itu semua konsumen dapat menggunakan properti tersebut seperti kolom DB biasa (dan tidak mereferensikan rakitan tambahan jika tidak diperlukan), tetapi server DB akan mengetahui bahwa itu adalah FK dan dapat menggunakan cascading. Solusi yang sangat kotor. Tapi berhasil.

ular berbisa
sumber