学海无涯

导航

EFCore 一对多 实体关系

一、一对多关系

单个实体任意数量的其他实体关联时,将使用一对多关系。

必需的一对多

//主体实体 Principal (parent)
public class Blog
{//博客,一个博客有多个帖子
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents 集合导航
}

//依赖实体 Dependent (child)
public class Post
{//帖子
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal 依赖导航
}

一对多关系由以下部分组成:

  • 主体实体上的一个或多个主键或备用键属性,即关系的“一”端。 例如 Blog.Id
  • 依赖实体上的一个或多个外键属性,即关系的“多”端。 例如 Post.BlogId
  • (可选)引用依赖实体的主体实体上的集合导航。 例如 Blog.Posts
  • (可选)引用主体实体的依赖实体上的引用导航。 例如 Post.Blog

因此,对于此示例中的关系:

  • 外键属性 Post.BlogId 不可为空。 这会使关系成为“必需”关系,因为每个依赖实体 (Post) 必须与某个主体实体 (Blog) 相关,而其外键属性必须设置为某个主体的Id值。
  • 这两个实体都有指向关系另一端的相关实体的导航。

 提示

具有两个导航的关系(一个是从依赖实体到主体实体,一个是从主体实体到依赖实体)称为双向关系。

 重要

使用 C# 可为空引用类型时,如果外键属性可为空,则引用导航必须不可为空。 如果外键属性不可为空,则引用导航可以为空,也可以不为空

显式配置实体关系(仅当默认约定无法满足实体之间的关系声明时才使用显式配置)

对于未按默认约定发现关系的导航外键必需/可选性质的情况,可以显式配置这些内容。 例如:  

1. 配置从主体实体开始

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

 2. 配置从依赖实体类型开始

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(e => e.Blog)
        .WithMany(e => e.Posts)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

提示:两配置方式并没有什么优势:它们都会导致完全相同的配置。  

注意

没有必要对关系进行两次配置,即先从主体实体开始,又从依赖实体开始。仅仅只需要编写一次配置代码。

 二、可选的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int? BlogId { get; set; } // Optional foreign key property
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

这与上一个示例相同,只不过外键属性和到主体实体的导航现在可为空。 这会使关系成为“可选”关系,因为依赖实体 (Post) 可以在无需与任何主体实体 (Blog) 相关的情况下存在。

重要

使用 C# 可为空引用类型时,如果外键属性可为空,则引用导航必须为空。

如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired(false);
}

三、具有阴影外键的必需的一对多(不推荐使用)即:没有显示声明外键,约定根据导航实体创建外键

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

 如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("BlogId")
        .IsRequired();
}

四、具有阴影外键的可选一对多(不推荐使用)

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("BlogId")
        .IsRequired(false);
}

五、无需导航到主体实体的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

依赖实体上的导航已被移除。  

提示

只有一个导航的关系(即从依赖实体到主体实体,或从主体实体到依赖实体,但只有其中一个)的被称单向关系。

如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

请注意,对 WithOne 的调用没有参数。 这是告知 EF 没有从 Post 到 Blog 的导航的方式。 

如果从没有导航的实体开始配置,则必须使用泛型 HasOne<>() 调用显式指定关系另一端的实体类型。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne<Blog>()
        .WithMany(e => e.Posts)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

六、 无需导航到主体实体且有阴影外键的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
}

  

此示例通过移除外键属性和依赖实体上的导航来合并上述两个示例。

此关系按约定作为可选关系被发现。 由于代码中没有任何内容可以用来指示它是必需的,因此需要使用 IsRequired 进行最小配置来创建必需关系。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .IsRequired();
}

可以使用更完整的配置来显式配置导航和外键名称,并根据需要对 IsRequired() 或 IsRequired(false) 进行适当的调用。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .HasForeignKey("BlogId")
        .IsRequired();
}

七、无需导航到依赖实体的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

  如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(e => e.Blog)
        .WithMany()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

 

请注意,WithMany() 调用时没有参数,可指示此方向没有导航。

如果从没有导航的实体开始配置,则必须使用泛型 HasMany<>() 调用显式指定关系另一端的实体类型。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

八、无导航的一对多(不会按约定发现关系,需显示配置关系)

有时,配置无导航的关系可能很有用。 此类关系只能通过直接更改外键值来操作。

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

此关系不会按约定发现,因为没有任何导航指示这两种类型是相关的。 可以在 OnModelCreating 中显式配置它。 例如:  

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne();
}

使用此配置时,按照约定,Post.BlogId 属性仍被检测为外键,并且关系是必需的,因为外键属性不可为空。 通过将外键属性设为“可为空”,可以使关系成为“可选”关系。

此关系的更完整显式配置是:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

九、具有备用键的一对多(需显示配置)

 在到目前为止的所有示例中,依赖实体上的外键属性被约束为主体实体上的主键(ID)属性。 外键可以改为被约束为不同的属性,该属性随后成为主体实体类型的备用键。 例如:

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public int AlternateId { get; set; } // Alternate key as target of the Post.BlogId foreign key
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

此关系不是按约定发现的,因为 EF 始终按照约定创建与主键的关系。 可以使用对 HasPrincipalKey 的调用在 OnModelCreating 中显式配置它。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId);
}

 HasPrincipalKey 可与其他调用结合使用,以显式配置导航、外键属性和必需/可选性质。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

十、具有复合外键的一对多

在到目前为止的所有示例中,主体实体的主键或备用键属性由单个属性组成。 主键或备用键也可以形成多个属性,这些属性称为“组合键”。 当关系的主体实体具有组合键时,依赖实体的外键也必须是具有相同属性数的组合键。 例如:

// Principal (parent)
public class Blog
{
    public int Id1 { get; set; } // Composite key part 1
    public int Id2 { get; set; } // Composite key part 2
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId1 { get; set; } // Required foreign key property part 1
    public int BlogId2 { get; set; } // Required foreign key property part 2
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

  此关系按约定发现。 但是,需要显式配置组合键本身:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(e => new { e.Id1, e.Id2 });
}

重要

如果组合外键值的任何属性值为空,则认为其值为 null。 具有一个属性“空”和另一个属性“非空”的组合外键不会被视为与具有相同值的主键或备用键匹配。 两者都将被视为 null

HasForeignKey 和 HasPrincipalKey 都可用于显式指定具有多个属性的键。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        nestedBuilder =>
        {
            nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });

            nestedBuilder.HasMany(e => e.Posts)
                .WithOne(e => e.Blog)
                .HasPrincipalKey(e => new { e.Id1, e.Id2 })
                .HasForeignKey(e => new { e.BlogId1, e.BlogId2 })
                .IsRequired();
        });
}

 提示

在上面的代码中,对 HasKey 和 HasMany 的调用已组合到嵌套生成器中。 使用嵌套生成器,无需为同一实体类型多次调用 Entity<>(),但在功能上等效于多次调用 Entity<>()

十一、无需级联删除的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

  按照约定,必需关系配置为级联删除,这意味着,删除主体实体后,也会删除其所有依赖实体,因为依赖实体无法存在于每月主体实体的数据库中。 可以将 EF 配置为引发异常,而不是自动删除不再存在的依赖行:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .OnDelete(DeleteBehavior.Restrict);
}

十二、自引用一对多

在前面的所有示例中,主体实体类型与依赖实体类型有所不同。 情况不一定如此。 例如,在下面的类型中,每个 Employee 都与另一个 Employees 相关。

public class Employee
{
    public int Id { get; set; }

    public int? ManagerId { get; set; } // Optional foreign key property
    public Employee? Manager { get; set; } // Optional reference navigation to principal
    public ICollection<Employee> Reports { get; } = new List<Employee>(); // Collection navigation containing dependents
}

  此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Employee>()
        .HasOne(e => e.Manager)
        .WithMany(e => e.Reports)
        .HasForeignKey(e => e.ManagerId)
        .IsRequired(false);
}

 

参考:https://learn.microsoft.com/zh-cn/ef/core/modeling/relationships/one-to-many  

 

  

 

 

  

  

  

 

 

  

  

 

  

  

  

 

  

  

posted on 2024-05-06 17:22  宁静致远.  阅读(22)  评论(0编辑  收藏  举报