รหัส EF รหัสแรกที่ไม่มีคุณสมบัติการนำทาง


93

สมมติว่าฉันมีเอนทิตีต่อไปนี้:

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

รหัสคืออะไรแรกไวยากรณ์ API ได้อย่างคล่องแคล่วในการบังคับใช้ที่ parentId จะถูกสร้างขึ้นในฐานข้อมูลที่มีข้อ จำกัด ที่สำคัญต่างประเทศในตารางผู้ปกครองโดยไม่จำเป็นต้องมีคุณสมบัตินำทาง ?

ฉันรู้ว่าถ้าฉันเพิ่มคุณสมบัติการนำทาง Parent ให้กับ Child ฉันจะทำได้:

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

แต่ฉันไม่ต้องการคุณสมบัติการนำทางในกรณีนี้โดยเฉพาะ


1
ฉันไม่คิดว่าสิ่งนี้เป็นไปได้ด้วย EF เพียงอย่างเดียวคุณอาจต้องใช้ SQL ดิบบางส่วนในการย้ายข้อมูลด้วยตนเองเพื่อตั้งค่า
ไม่เป็นที่รัก

@LukeMcGregor นั่นคือสิ่งที่ฉันกลัว หากคุณให้คำตอบฉันยินดีที่จะยอมรับโดยถือว่าถูกต้อง :-)
RationalGeek

มีเหตุผลเฉพาะที่ไม่มีคุณสมบัติการนำทางหรือไม่? จะทำให้คุณสมบัติการนำทางเป็นส่วนตัวสำหรับคุณ - จะไม่ปรากฏให้เห็นภายนอกเอนทิตี แต่โปรด EF (หมายเหตุฉันยังไม่ได้ลอง แต่คิดว่าน่าจะใช้ได้ - ลองดูโพสต์นี้เกี่ยวกับการทำแผนที่คุณสมบัติส่วนตัวromiller.com/2012/10/01/… )
Pawel

6
ฉันไม่ต้องการเพราะฉันไม่ต้องการมัน ฉันไม่ชอบที่จะต้องใส่สิ่งที่ไม่จำเป็นเพิ่มเติมในการออกแบบเพียงเพื่อตอบสนองความต้องการของกรอบงาน มันจะฆ่าฉันใส่เสานำทางหรือไม่? ไม่อันที่จริงนั่นคือสิ่งที่ฉันได้ทำในขณะนี้
RationalGeek

คุณต้องมีคุณสมบัติการนำทางอย่างน้อยหนึ่งด้านเสมอเพื่อสร้างความสัมพันธ์ สำหรับข้อมูลเพิ่มเติมstackoverflow.com/a/7105288/105445
Wahid Bitar

คำตอบ:


63

ด้วย EF Code First Fluent API เป็นไปไม่ได้ คุณต้องมีคุณสมบัติการนำทางอย่างน้อยหนึ่งรายการเสมอเพื่อสร้างข้อ จำกัด ของคีย์นอกในฐานข้อมูล

หากคุณใช้ Code First Migrations คุณมีตัวเลือกในการเพิ่มการย้ายข้อมูลโดยใช้รหัสใหม่บนคอนโซลตัวจัดการแพ็คเกจ ( add-migration SomeNewSchemaName) หากคุณเปลี่ยนแปลงบางอย่างกับโมเดลของคุณหรือการจับคู่การย้ายข้อมูลใหม่จะถูกเพิ่มเข้ามา หากคุณไม่ได้เปลี่ยนแปลงอะไรบังคับให้ย้ายข้อมูลใหม่โดยใช้add-migration -IgnoreChanges SomeNewSchemaName. การโยกย้ายจะมีที่ว่างเปล่าUpและDownวิธีการในกรณีนี้

จากนั้นคุณสามารถปรับเปลี่ยนUpวิธีการได้โดยเพิ่ม follwing เข้าไป:

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

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

การเรียกใช้การย้ายข้อมูลนี้ ( update-databaseบนคอนโซลการจัดการแพ็คเกจ) จะเรียกใช้คำสั่ง SQL ที่คล้ายกับสิ่งนี้ (สำหรับ SQL Server):

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

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

หรือหากไม่มีการโยกย้ายคุณสามารถเรียกใช้คำสั่ง SQL ที่แท้จริงโดยใช้

context.Database.ExecuteSqlCommand(sql);

contextอินสแตนซ์ของคลาสบริบทที่ได้รับของคุณอยู่ที่ไหนและsqlเป็นเพียงคำสั่ง SQL ด้านบนเป็นสตริง

โปรดทราบว่า EF ทั้งหมดนี้ไม่มีเงื่อนงำที่ParentIdเป็นคีย์ต่างประเทศที่อธิบายความสัมพันธ์ EF จะพิจารณาว่าเป็นคุณสมบัติสเกลาร์ธรรมดาเท่านั้น อย่างไรก็ตามทั้งหมดข้างต้นเป็นเพียงวิธีที่ซับซ้อนและช้ากว่าเมื่อเทียบกับการเปิดเครื่องมือจัดการ SQL และเพิ่มข้อ จำกัด ด้วยมือ


2
การทำให้เข้าใจง่ายมาจากระบบอัตโนมัติ: ฉันไม่สามารถเข้าถึงสภาพแวดล้อมอื่น ๆ ที่ใช้รหัสของฉันได้ ความสามารถในการเปลี่ยนแปลงโค้ดเหล่านี้เป็นสิ่งที่ดีสำหรับฉัน แต่ฉันชอบ snark :)
pomeroy

ผมคิดว่าคุณก็เป็นเพียงแค่ตั้งค่าแอตทริบิวต์นิติบุคคลดังนั้นใน id [ForeignKey("ParentTableName")]ผู้ปกครองเพียงเพิ่ม ซึ่งจะเชื่อมโยงคุณสมบัติกับคีย์ใด ๆ ที่อยู่บนตารางหลัก ตอนนี้คุณมีชื่อตารางแบบฮาร์ดโค้ดแล้ว
Triynko

2
เห็นได้ชัดว่าเป็นไปไม่ได้ดูความคิดเห็นอื่น ๆ ด้านล่างทำไมสิ่งนี้จึงถูกทำเครื่องหมายว่าเป็นคำตอบที่ถูกต้อง
Igor Be

113

แม้ว่าโพสต์นี้Entity Frameworkไม่Entity Framework Coreได้มีไว้สำหรับคนที่ต้องการบรรลุสิ่งเดียวกันโดยใช้ Entity Framework Core (ฉันใช้ V1.1.2)

ฉันไม่ต้องการคุณสมบัติการนำทาง (แม้ว่ามันจะดีก็ตาม) เพราะฉันฝึก DDD และฉันต้องการParentและChildเป็นสองรากรวมที่แยกจากกัน ฉันต้องการให้พวกเขาสามารถพูดคุยกันผ่านคีย์ต่างประเทศไม่ใช่ผ่านEntity Frameworkคุณสมบัติการนำทางเฉพาะโครงสร้างพื้นฐาน

สิ่งที่คุณต้องทำคือกำหนดค่าความสัมพันธ์ด้านหนึ่งโดยใช้HasOneและWithManyไม่ระบุคุณสมบัติการนำทาง (ไม่ได้อยู่ที่นั่นเลย)

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

        ......
    }
}

ฉันกำลังให้ออกตัวอย่างเกี่ยวกับวิธีการกำหนดค่าคุณสมบัตินิติบุคคลเช่นกัน แต่สิ่งที่สำคัญที่สุดที่นี่คือHasOne<>, และWithMany()HasForeignKey()

หวังว่าจะช่วยได้


9
นี่คือคำตอบที่ถูกต้องสำหรับ EF Core และสำหรับผู้ที่ฝึก DDD นี่คือสิ่งที่ต้องทำ
Thiago Silva

1
ฉันไม่ชัดเจนว่ามีอะไรเปลี่ยนแปลงเพื่อลบคุณสมบัติการนำทาง คุณช่วยชี้แจงได้ไหม?
andrew.rockwell

3
@ andrew.rockwell: ดูHasOne<Parent>()และ.WithMany()เกี่ยวกับการกำหนดค่าลูกของพวกเขา พวกเขาไม่ได้อ้างอิงคุณสมบัติการนำทางเลยเนื่องจากไม่มีการกำหนดคุณสมบัติการนำทางไว้ ฉันจะพยายามทำให้ชัดเจนขึ้นด้วยการอัปเดตของฉัน
David Liang

น่ากลัว ขอบคุณ @DavidLiang
andrew.rockwell

2
@ mirind4 ไม่เกี่ยวข้องกับตัวอย่างโค้ดเฉพาะใน OP หากคุณกำลังแมปเอนทิตีรูทรวมที่แตกต่างกันกับตาราง DB ตาม DDD เอนทิตีรูทเหล่านั้นควรอ้างอิงซึ่งกันและกันผ่านข้อมูลประจำตัวเท่านั้นและไม่ควรมีการอ้างอิงแบบเต็ม ไปยังเอนทิตี AR อื่น ในความเป็นจริงใน DDD เป็นเรื่องปกติที่จะทำให้ข้อมูลประจำตัวของเอนทิตีเป็นออบเจ็กต์ที่มีค่าแทนที่จะใช้อุปกรณ์ประกอบฉากที่มีประเภทดั้งเดิมเช่น int / log / guid (โดยเฉพาะเอนทิตี AR) หลีกเลี่ยงความหลงใหลแบบดั้งเดิมและยังอนุญาตให้ AR ต่างๆอ้างอิงเอนทิตีผ่านค่า ประเภทรหัสออบเจ็กต์ HTH
Thiago Silva

21

คำแนะนำเล็กน้อยสำหรับผู้ที่ต้องการใช้ DataAnotations และไม่ต้องการเปิดเผยคุณสมบัติการนำทาง - ใช้ 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; }
}

นั่นคือ - คีย์ต่างประเทศที่มีcascade:trueafter Add-Migrationจะถูกสร้างขึ้น


1
สามารถทำได้โดยส่วนตัว
Marc Wittke

2
เมื่อสร้าง Child คุณจะต้องกำหนดParentหรือเพียงแค่ParentId?
George Mauer

5
@MarcWittke คุณสมบัติไม่สามารถvirtual private
LINQ

@GeorgeMauer: นั่นเป็นคำถามที่ดี! ในทางเทคนิคอาจใช้งานได้ แต่ก็จะกลายเป็นปัญหาเมื่อคุณมีรหัสที่ไม่สอดคล้องกันเช่นนี้เนื่องจากนักพัฒนา (โดยเฉพาะผู้มาใหม่) ไม่แน่ใจว่าจะส่งผ่านอะไร
David Liang

15

ในกรณีของEF Coreคุณไม่จำเป็นต้องระบุคุณสมบัติการนำทาง คุณสามารถระบุ Foreign Key ที่ด้านใดด้านหนึ่งของความสัมพันธ์ ตัวอย่างง่ายๆของ 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; }
    }
}

2

ฉันใช้. Net Core 3.1, EntityFramework 3.1.3 ฉันค้นหาไปรอบ ๆ และโซลูชันที่ฉันคิดขึ้นมาก็ใช้เวอร์ชันทั่วไปของHasForeginKey<DependantEntityType>(e => e.ForeginKeyProperty). คุณสามารถสร้างความสัมพันธ์แบบหนึ่งต่อหนึ่งดังนี้:

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

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

หวังว่านี่จะช่วยหรืออย่างน้อยก็ให้แนวคิดอื่น ๆ เกี่ยวกับวิธีใช้HasForeginKeyวิธีนี้


0

เหตุผลของฉันที่ไม่ใช้คุณสมบัติการนำทางคือการอ้างอิงคลาส ฉันแยกโมเดลออกเป็นแอสเซมบลีไม่กี่ชิ้นซึ่งสามารถใช้หรือไม่ใช้ในโปรเจ็กต์ต่างๆในชุดใดก็ได้ ดังนั้นถ้าฉันมีเอนทิตีที่มีคุณสมบัติการตั้งค่าเป็นคลาสจากแอสเซมบลีอื่นฉันจำเป็นต้องอ้างอิงแอสเซมบลีนั้นซึ่งฉันต้องการหลีกเลี่ยง (หรือโปรเจ็กต์ใด ๆ ที่ใช้ส่วนหนึ่งของโมเดลข้อมูลที่สมบูรณ์นั้นจะนำทุกอย่างไปด้วย)

และฉันมีแอปการย้ายข้อมูลแยกต่างหากซึ่งใช้สำหรับการย้ายข้อมูล (ฉันใช้การกำหนดค่าอัตโนมัติ) และการสร้างฐานข้อมูลเริ่มต้น โครงการนี้อ้างอิงทุกอย่างด้วยเหตุผลที่ชัดเจน

โซลูชันคือ C-style:

  • "คัดลอก" ไฟล์ที่มีคลาสเป้าหมายไปยังโปรเจ็กต์การย้ายผ่านลิงก์ (ลาก n-drop ด้วยaltคีย์ใน VS)
  • ปิดการใช้งานคุณสมบัติการตั้งค่า (และแอตทริบิวต์ FK) ผ่าน #if _MIGRATION
  • ตั้งค่าคำจำกัดความของตัวประมวลผลล่วงหน้าในแอปการย้ายข้อมูลและไม่ได้ตั้งค่าในโครงการโมเดลดังนั้นจะไม่อ้างอิงอะไรเลย (อย่าอ้างอิงแอสเซมบลีกับContactคลาสในตัวอย่าง)

ตัวอย่าง:

    public int? ContactId { get; set; }

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

แน่นอนคุณควรปิดการใช้งานusingคำสั่งและเปลี่ยนเนมสเปซด้วยวิธีเดียวกัน

หลังจากนั้นผู้บริโภคทุกคนสามารถใช้คุณสมบัตินั้นได้ตามปกติฟิลด์ DB (และไม่ต้องอ้างอิงแอสเซมบลีเพิ่มเติมหากไม่จำเป็น) แต่เซิร์ฟเวอร์ DB จะรู้ว่าเป็น FK และสามารถใช้การเรียงซ้อนได้ สารละลายสกปรกมาก แต่ใช้งานได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.