Mã EF Khóa ngoại đầu tiên không có thuộc tính điều hướng


91

Giả sử tôi có các thực thể sau:

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

Mã cú pháp API thông thạo đầu tiên để thực thi ParentId được tạo trong cơ sở dữ liệu với ràng buộc khóa ngoại đối với bảng Cha mà không cần phải có thuộc tính điều hướng là gì?

Tôi biết rằng nếu tôi thêm thuộc tính điều hướng Parent to Child, thì tôi có thể thực hiện việc này:

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

Nhưng tôi không muốn thuộc tính điều hướng trong trường hợp cụ thể này.


1
Tôi không nghĩ rằng đây là thực sự có thể chỉ với EF, bạn có thể cần phải sử dụng một số SQL liệu trong một di chuyển bằng tay để thiết lập nó
Không yêu

@LukeMcGregor đó là điều tôi lo sợ. Nếu bạn đưa ra câu trả lời đó, tôi sẽ vui lòng chấp nhận nó, cho rằng nó là chính xác. :-)
RationalGeek

Có lý do cụ thể nào cho việc không có thuộc tính điều hướng không? Việc đặt thuộc tính điều hướng có hoạt động riêng tư cho bạn không - sẽ không hiển thị bên ngoài thực thể nhưng sẽ làm hài lòng EF. (Lưu ý rằng tôi chưa thử cách này nhưng tôi nghĩ cách này sẽ hoạt động - hãy xem bài đăng này về ánh xạ các thuộc tính riêng tư romiller.com/2012/10/01/… )
Pawel

6
Tôi không muốn nó vì tôi không cần nó. Tôi không thích phải thêm những thứ không cần thiết vào một thiết kế chỉ để đáp ứng các yêu cầu của một khuôn khổ. Nó có giết tôi để đưa vào chỗ dựa điều hướng không? Không. Thực tế đó là những gì tôi đã làm trong thời điểm hiện tại.
RationalGeek

Bạn luôn cần thuộc tính điều hướng ở ít nhất một bên để xây dựng mối quan hệ. Để biết thêm thông tin stackoverflow.com/a/7105288/105445
Wahid Bitar

Câu trả lời:


63

Với EF Code First Fluent API thì điều đó là không thể. Bạn luôn cần ít nhất một thuộc tính điều hướng để tạo ràng buộc khóa ngoại trong cơ sở dữ liệu.

Nếu bạn đang sử dụng Code First Migrations, bạn có tùy chọn để thêm di chuyển dựa trên mã mới trên bảng điều khiển trình quản lý gói ( add-migration SomeNewSchemaName). Nếu bạn đã thay đổi một cái gì đó với mô hình của mình hoặc ánh xạ một quá trình di chuyển mới sẽ được thêm vào. Nếu bạn không thay đổi bất cứ điều gì, hãy bắt buộc di chuyển mới bằng cách sử dụng add-migration -IgnoreChanges SomeNewSchemaName. Trong trường hợp này, quá trình di chuyển sẽ chỉ chứa rỗng UpDowncác phương thức.

Sau đó, bạn có thể sửa đổi Upphương thức bằng cách thêm follwing vào nó:

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

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

Chạy quá trình di chuyển này ( update-databasetrên bảng điều khiển quản lý gói) sẽ chạy một câu lệnh SQL tương tự như sau (cho SQL Server):

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

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

Ngoài ra, không cần di chuyển, bạn chỉ có thể chạy một lệnh SQL thuần túy bằng cách sử dụng

context.Database.ExecuteSqlCommand(sql);

đâu contextlà một thể hiện của lớp ngữ cảnh dẫn xuất của bạn và sqlchỉ là lệnh SQL ở trên dưới dạng chuỗi.

Lưu ý rằng với tất cả EF này, không có manh mối nào ParentIdlà khóa ngoại mô tả một mối quan hệ. EF sẽ chỉ coi nó như một thuộc tính vô hướng thông thường. Bằng cách nào đó, tất cả những điều trên chỉ là một cách phức tạp hơn và chậm hơn so với việc chỉ mở một công cụ quản lý SQL và thêm các ràng buộc bằng tay.


2
Sự đơn giản hóa đến từ sự tự động hóa: Tôi không có quyền truy cập vào các môi trường khác mà mã của tôi được triển khai. Với tôi, có thể thực hiện những thay đổi này trong mã. Nhưng tôi thích sự rắn rỏi :)
pomeroy

Tôi nghĩ rằng bạn cũng chỉ cần đặt một thuộc tính trên thực thể, vì vậy trên id mẹ, chỉ cần thêm [ForeignKey("ParentTableName")]. Điều đó sẽ liên kết thuộc tính với bất kỳ khóa nào có trên bảng mẹ. Bây giờ bạn đã có một tên bảng được mã hóa cứng.
Triynko

2
Đó là rõ ràng không phải là không thể, nhìn thấy những nhận xét khác dưới đây Tại sao điều này thậm chí còn được đánh dấu là câu trả lời đúng
Igor Be

111

Mặc dù bài đăng này Entity Frameworkkhông phải là không Entity Framework Core, nhưng nó có thể hữu ích cho những người muốn đạt được điều tương tự bằng cách sử dụng Entity Framework Core (tôi đang sử dụng V1.1.2).

Tôi không cần các thuộc tính điều hướng (mặc dù chúng đẹp) bởi vì tôi đang thực hành DDD và tôi muốn ParentChildlà hai gốc tổng hợp riêng biệt. Tôi muốn họ có thể nói chuyện với nhau thông qua khóa ngoại không phải thông qua các Entity Frameworkthuộc tính điều hướng dành riêng cho cơ sở hạ tầng .

Tất cả những gì bạn phải làm là định cấu hình mối quan hệ ở một phía bằng cách sử dụng HasOneWithManykhông chỉ định các thuộc tính điều hướng (sau cùng thì chúng không có ở đó).

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

        ......
    }
}

Tôi cũng đưa ra các ví dụ về cách định cấu hình các thuộc tính thực thể, nhưng điều quan trọng nhất ở đây là HasOne<>, WithMany()HasForeignKey().

Hy vọng nó giúp.


8
Đây là câu trả lời chính xác cho EF Core, và đối với những người thực hành DDD, đây là câu PHẢI.
Thiago Silva

1
Tôi không rõ điều gì đã thay đổi để xóa thuộc tính điều hướng. Bạn có thể vui lòng làm rõ?
andrew.rockwell

3
@ andrew.rockwell: Xem cấu hình con HasOne<Parent>().WithMany()trên của bạn. Chúng hoàn toàn không tham chiếu đến các thuộc tính điều hướng, vì không có thuộc tính điều hướng nào được xác định. Tôi sẽ cố gắng làm cho nó rõ ràng hơn với các bản cập nhật của mình.
David Liang

Tuyệt vời. Cảm ơn @DavidLiang
andrew.rockwell 26/02/19

2
@ mirind4 không liên quan đến mẫu mã cụ thể trong OP, nếu bạn đang ánh xạ các thực thể gốc tổng hợp khác nhau tới các bảng DB tương ứng của chúng, theo DDD, các tệp gốc đó chỉ nên tham chiếu lẫn nhau thông qua danh tính của chúng và không được chứa tham chiếu đầy đủ đến thực thể AR khác. Trên thực tế, trong DDD, người ta cũng thường đặt danh tính của các thực thể thành một đối tượng giá trị thay vì sử dụng các đạo cụ có kiểu nguyên thủy như int / log / hướng dẫn (đặc biệt là các thực thể AR), tránh ám ảnh nguyên thủy và cũng cho phép các AR khác nhau tham chiếu đến các thực thể thông qua giá trị loại ID đối tượng. HTH
Thiago Silva

21

Gợi ý nhỏ cho những ai muốn sử dụng DataAnotations và không muốn để lộ Thuộc tính điều hướng - hãy sử dụng 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; }
}

Đó là nó - khóa ngoại với cascade:trueafter Add-Migrationsẽ được tạo.


1
Nó thậm chí có thể bởi tin
Marc Wittke

2
Khi tạo Child bạn sẽ phải gán Parenthay chỉ ParentId?
George Mauer

5
Thuộc virtualtính @MarcWittke không được private.
LINQ

@GeorgeMauer: đó là một câu hỏi hay! Về mặt kỹ thuật, hoặc người ta sẽ làm việc, nhưng nó cũng trở thành vấn đề khi bạn có mã số không phù hợp như thế này, bởi vì các nhà phát triển (đặc biệt là những người mới đến) không chắc chắn những gì để vượt qua trong.
David Liang

14

Trong trường hợp EF Core, bạn không nhất thiết phải cung cấp thuộc tính điều hướng. Bạn có thể chỉ cần cung cấp Khóa ngoại ở một phía của mối quan hệ. Một ví dụ đơn giản với API Fluent:

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

Tôi đang sử dụng .Net Core 3.1, EntityFramework 3.1.3. Tôi đã tìm kiếm xung quanh và Giải pháp tôi đã đưa ra là sử dụng phiên bản chung của HasForeginKey<DependantEntityType>(e => e.ForeginKeyProperty). bạn có thể tạo mối quan hệ 1-1 như sau:

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

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

Hy vọng điều này sẽ giúp hoặc ít nhất cung cấp một số ý tưởng khác về cách sử dụng HasForeginKeyphương pháp.


0

Lý do của tôi cho việc không sử dụng thuộc tính điều hướng là sự phụ thuộc của lớp. Tôi đã tách các mô hình của mình thành một vài cụm, có thể được sử dụng hoặc không được sử dụng trong các dự án khác nhau trong bất kỳ tổ hợp nào. Vì vậy, nếu tôi có thực thể có thuộc tính nagivation đến lớp từ một assembly khác, tôi cần tham chiếu đến assembly đó, điều mà tôi muốn tránh (hoặc bất kỳ dự án nào sử dụng một phần của mô hình dữ liệu hoàn chỉnh đó sẽ mang theo mọi thứ).

Và tôi có ứng dụng di chuyển riêng biệt, được sử dụng để di chuyển (tôi sử dụng tự động hóa) và tạo DB ban đầu. Dự án này tham chiếu mọi thứ bởi những lý do rõ ràng.

Giải pháp là kiểu C:

  • "sao chép" tệp có lớp đích vào dự án di chuyển qua liên kết (kéo-n-thả bằng altkhóa trong VS)
  • vô hiệu hóa thuộc tính nagivation (và thuộc tính FK) qua #if _MIGRATION
  • đặt định nghĩa tiền xử lý đó trong ứng dụng di chuyển và không đặt trong dự án mô hình, do đó, nó sẽ không tham chiếu bất kỳ thứ gì (không tham chiếu assembly với Contactlớp chẳng hạn).

Mẫu vật:

    public int? ContactId { get; set; }

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

Tất nhiên, bạn cũng nên vô hiệu hóa usingchỉ thị và thay đổi vùng tên.

Sau đó, tất cả người tiêu dùng có thể sử dụng thuộc tính đó như trường DB thông thường (và không tham chiếu đến các tập hợp bổ sung nếu chúng không cần thiết), nhưng máy chủ DB sẽ biết rằng đó là FK và có thể sử dụng xếp tầng. Dung dịch rất bẩn. Nhưng hoạt động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.