繁体   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