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

时间:2014-01-02 15:23:09

标签: 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; }
}

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

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

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

但在这种特殊情况下我不想要导航属性。

6 个答案:

答案 0 :(得分:55)

虽然这篇文章适用于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()

希望它有所帮助。

答案 1 :(得分:53)

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

如果您使用的是代码优先迁移,则可以选择在包管理器控制台(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是派生上下文类的实例,而sql只是上面的SQL命令字符串。

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

答案 2 :(得分:15)

对于那些想要使用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:true之后Add-Migration的外键。

答案 3 :(得分: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方法的想法。

答案 4 :(得分:0)

对于EF Core,您不一定需要提供导航属性。您只需在关系的一侧提供外键即可。一个使用Fluent API的简单示例:

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

    namespace EFModeling.Configuring.FluentAPI.Samples.Relationships.NoNavigation
    {
        #region Model
        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; }
        }
        #endregion
    }

答案 5 :(得分:0)

我之所以不使用导航属性是因为类依赖。我将模型分离为几个程序集,这些程序集可以在任何组合中使用或不使用。因此,如果我有一个具有导航属性的实体可以从另一个程序集中进行分类,则需要引用该程序集,而我想避免该程序(否则,使用该完整数据模型的一部分的任何项目都将包含该程序集)。

我有一个单独的迁移应用程序,用于迁移(我使用自动迁移)和初始数据库创建。该项目出于显而易见的原因引用了所有内容。

解决方案是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并可以使用级联。非常脏的解决方案。但是有效。