簡體   English   中英

沒有導航屬性的EF Code First外鍵

[英]EF Code First foreign key without navigation property

假設我有以下實體:

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

什么是代碼優先的流暢 API 語法,以強制在數據庫中創建 ParentId,並使用對Parents 表的外鍵約束,而無需具有導航屬性

我知道,如果我將導航屬性 Parent 添加到 Child,那么我可以這樣做:

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

但是在這種特殊情況下我不想要導航屬性。

雖然這篇文章是針對Entity Framework而不是Entity Framework Core ,但對於想要使用 Entity Framework Core(我使用的是 V1.1.2)來實現相同目標的人來說,它可能很有用。

我不需要導航屬性(雖然它們很好),因為我正在練習 DDD 並且我希望ParentChild是兩個單獨的聚合根。 我希望他們能夠通過外鍵而不是通過特定於基礎結構的Entity Framework導航屬性相互交談。

您所要做的就是使用HasOneWithMany在一側配置關系,而不指定導航屬性(它們畢竟不存在)。

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()

希望它有幫助。

使用 EF Code First Fluent API 是不可能的。 您始終需要至少一個導航屬性來在數據庫中創建外鍵約束。

如果您使用的是 Code First 遷移,您可以選擇在包管理器控制台上添加新的基於代碼的遷移 ( add-migration SomeNewSchemaName )。 如果您對模型進行了更改或映射,則會添加新的遷移。 如果您沒有更改任何內容,請使用add-migration -IgnoreChanges SomeNewSchemaName強制進行新遷移。 在這種情況下,遷移將只包含空的UpDown方法。

然后,您可以通過向其添加以下內容來修改Up方法:

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是您派生的 context 類的實例,而sql只是上述 SQL 命令作為字符串。

請注意,所有這些 EF 都不知道ParentId是描述關系的外鍵。 EF 只會將其視為普通的標量屬性。 不知何故,與僅打開 SQL 管理工具並手動添加約束相比,上述所有方法只是一種更復雜和更慢的方法。

對於EF Core,您不一定需要提供導航屬性。 您可以簡單地在關系的一側提供一個外鍵。 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; }
    }
}

對於那些想要使用 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; }
}

就是這樣 - 將創建Add-Migration后帶有cascade:true的外鍵cascade:true

我正在使用 .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方法的其他想法。

使用導航屬性的原因是類依賴性。 我將我的模型分成幾個組件,這些組件可以在不同項目中以任意組合使用或不使用。 因此,如果我的實體具有從另一個程序集進行分類的導航屬性,我需要引用該程序集,我想避免這種情況(或任何使用該完整數據模型的一部分的項目都將攜帶所有內容)。

我有單獨的遷移應用程序,用於遷移(我使用自動遷移)和初始數據庫創建。 該項目通過顯而易見的原因引用了所有內容。

解決方案是 C 風格的:

  • 通過鏈接將帶有目標類的文件“復制”到遷移項目(在 VS 中使用alt鍵拖放)
  • 通過#if _MIGRATION禁用導航屬性(和 FK 屬性)
  • 在遷移應用程序中設置該預處理器定義並且不在模型項目中設置,因此它不會引用任何內容(在示例中不要引用帶有Contact類的程序集)。

樣品:

    public int? ContactId { get; set; }

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

當然,您應該以同樣的方式禁用using指令並更改命名空間。

之后,所有使用者都可以像往常一樣使用該屬性作為 DB 字段(如果不需要,則不要引用其他程序集),但 DB 服務器將知道它是 FK 並且可以使用級聯。 很臟的解決方案。 但是有效。

請問從實體類中刪除導航屬性有什么好處? 那我們可以使用急切的加載表格嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM