EF代码第一个没有导航属性的外键


91

假设我有以下实体:

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

什么是代码首先有效的API语法,以强制在数据库中创建ParentId并具有对Parents表的外键约束,而无需具有导航属性

我知道,如果我将导航属性“父级”添加到“子级”,则可以执行以下操作:

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

Answers:


63

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

如果您正在使用代码优先迁移,则可以选择在软件包管理器控制台(add-migration SomeNewSchemaName)上添加基于代码的新迁移。如果您对模型进行了更改或映射,则将添加新的迁移。如果您没有进行任何更改,请使用强制进行新的迁移add-migration -IgnoreChanges SomeNewSchemaName。在这种情况下,迁移将仅包含emptyUpDown方法。

然后,您可以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 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

110

虽然这个职位是Entity Framework不是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()

希望能帮助到你。


8
这是EF Core的正确答案,而对于那些练习DDD的人,这是必须的。
Thiago Silva

1
我不清楚删除导航属性的更改。你能澄清一下吗?
andrew.rockwell

3
@ andrew.rockwell:请参阅HasOne<Parent>().WithMany()有关子配置。它们根本没有引用导航属性,因为无论如何都没有定义导航属性。我将尝试通过更新使其更加清晰。
大卫·梁

太棒了 感谢@DavidLiang
andrew.rockwell

2
@ mirind4与OP中的特定代码示例无关,如果根据DDD将不同的聚合根实体映射到其各自的DB表,则这些根实体仅应通过其身份相互引用,并且不应包含完整引用到另一个AR实体。实际上,在DDD中,通常将实体的标识设为值对象,而不是使用具有诸如int / log / guid之类的原始类型的道具(尤其是AR实体),避免原始的迷恋并且还允许不同的AR通过值来引用实体对象ID类型。HTH
Thiago Silva

21

对于那些想使用DataAnotations并且不想公开Navigation属性的人的小提示-使用 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 2015年

5
@MarcWittkevirtual属性不能为private
LINQ

@GeorgeMauer:这是一个好问题!从技术上讲任何一个会工作,但它也成为问题,当你有这样的不一致的代码,因为开发人员(尤其是新来的)是不知道该怎么在传递。
梁大卫

14

如果使用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; }
    }
}

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风格的:

  • 通过链接将目标类的文件“复制”到迁移项目(使用altVS中的键进行拖放操作)
  • 通过以下方式禁用导航属性(和FK属性) #if _MIGRATION
  • 在迁移应用程序中设置该预处理器定义,而不在模型项目中设置,因此它不会引用任何内容(Contact在示例中,请勿引用带有类的程序集)。

样品:

    public int? ContactId { get; set; }

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

当然,您应该以相同的方式禁用using指令并更改名称空间。

之后,所有使用者都可以像平常的DB字段一样使用该属性(如果不需要,则不要引用其他程序集),但是DB服务器将知道它是FK并可以使用级联。非常脏的解决方案。但是有效。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.