EF Code First没有导航属性的外键

Rat*_*eek 65 entity-framework ef-code-first

假设我有以下实体:

public class Parent
{
    public int Id { get; set; }
}
public class Child
{
    public int Id { get; set; }
    public int ParentId { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

什么是代码第一个流畅的API语法来强制在数据库中使用Parent表的外键约束创建ParentId,而不需要具有导航属性

我知道如果我将一个导航属性Parent添加到Child,那么我可以这样做:

modelBuilder.Entity<Child>()
    .HasRequired<Parent>(c => c.Parent)
    .WithMany()
    .HasForeignKey(c => c.ParentId);
Run Code Online (Sandbox Code Playgroud)

但我不希望在这种特殊情况下使用导航属性.

Dav*_*ang 70

虽然这篇文章Entity Framework不是Entity Framework Core,但对于想要使用Entity Framework Core实现相同功能的人(我使用的是V1.1.2)可能会有用.

我不需要导航属性(尽管它们很好),因为我正在练习DDD,我想要Parent并且Child是两个独立的聚合根.我希望他们能够通过外键彼此交谈,而不是通过基础设施特定的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");
        });

        ......
    }
}
Run Code Online (Sandbox Code Playgroud)

我给出了如何配置实体属性的示例,但这里最重要的是HasOne<>,WithMany()HasForeignKey().

希望能帮助到你.

  • @andrew.rockwell:请参阅关于 thie 子配置的 `HasOne&lt;Parent&gt;()` 和 `.WithMany()`。它们根本不引用导航属性,因为无论如何都没有定义导航属性。我将尝试通过我的更新使其更清晰。 (3认同)
  • 这是EF Core的正确答案,而对于那些练习DDD的人,这是必须的。 (2认同)
  • @mirind4与OP中的特定代码示例无关,如果您将不同的聚合根实体映射到各自的数据库表,根据DDD,这些根实体应该仅通过其身份相互引用,并且不应包含完整引用到另一个 AR 实体。事实上,在 DDD 中,将实体的标识作为值对象而不是使用像 int/log/guid 这样的原始类型的 props(尤其是 AR 实体)也是很常见的,避免了原始的痴迷,也允许不同的 AR 通过值引用实体对象 ID 类型。华泰 (2认同)

Sla*_*uma 55

使用EF Code First Fluent API是不可能的.始终需要至少一个导航属性才能在数据库中创建外键约束.

如果您使用的是Code First Migrations,则可以选择在程序包管理器控制台(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
}
Run Code Online (Sandbox Code Playgroud)

运行此迁移(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])
Run Code Online (Sandbox Code Playgroud)

或者,如果没有迁移,您可以使用运行纯SQL命令

context.Database.ExecuteSqlCommand(sql);
Run Code Online (Sandbox Code Playgroud)

where context是派生上下文类的实例,并且sql只是上面的SQL命令作为字符串.

请注意,尽管如此,EF并不知道ParentId是描述关系的外键.EF将其视为普通的标量属性.与仅打开SQL管理工具并手动添加约束相比,上述所有方法只是一种更复杂,更慢的方式.

  • 这显然不是不可能的,请参阅下面的其他评论为什么这甚至被标记为正确答案 (3认同)
  • 简化来自自动化:我无法访问我的代码部署到的其他环境。能够在代码中执行这些更改对我来说很好。但我喜欢斯纳克 :) (2认同)

Jon*_*gon 25

对于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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案非常好,因为它使实体的定义更加精简、简单,并且与正在使用的 ORM 松散耦合。 (2认同)

ten*_*its 18

对于那些想要使用DataAnotations但不想公开Navigation Property的人的小提示 - 使用 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; }
}
Run Code Online (Sandbox Code Playgroud)

多数民众赞成 - 将创建具有cascade:trueafter 的外键Add-Migration.

  • @MarcWittke`virtual`属性不能是`private`. (5认同)
  • 创建Child时,你必须指定`Parent`或只是`ParentId`? (2认同)

小智 5

我正在使用.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();
Run Code Online (Sandbox Code Playgroud)

希望这会有所帮助,或者至少提供一些有关如何使用该HasForeginKey方法的其他想法。