C# EFCore 一对多配置(单向和双向导航属性配置)

EF Core 中的一对多关系实现
在现实世界的应用程序中,我们经常需要处理不同实体之间的关系。例如,在博客系统中,文章 (Article) 和评论 (Comment) 之间存在一对多的关系。这篇文章将介绍如何在 Entity Framework Core (EF Core) 中实现一对多关系,并通过具体的代码示例来演示不同的操作。

实体定义
首先,我们需要定义两个实体:Article 和 Comment。Article 表示一篇文章,而 Comment 则表示针对某篇文章的一个评论。由于一个文章可以有多个评论,因此我们定义了一个一对多的关系。

public class Article
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Message { get; set; }

    public ICollection<Comment> ArtComments { get; set; }
}

public class Comment
{
    public int Id { get; set; }
    public int ComArticleId { get; set; }
    public string ComMessage { get; set; }

    public Article ComArticle { get; set; }
}

执行Add-Migration xx 和Update-Database 后生成数据表

配置一对多关系
在 EF Core 中配置一对多关系可以通过两种方式完成:使用流利 API 或者使用数据注解。在这个例子中,我们将使用流利 API 来配置关系,在DbContext中配置也可以

// DbContext中配置
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<Article>()
        .HasMany(a => a.ArtComments)
        .WithOne(c => c.ComArticle)
        .HasForeignKey(c => c.ComArticleId);
}
// 在CommentConfig中配置也可以
 internal class CommentConfig : IEntityTypeConfiguration<Comment>
 {
     public void Configure(EntityTypeBuilder<Comment> builder)
     {
         builder.ToTable("T_Comment");
         builder.Property(p => p.ComMessage).IsRequired().HasMaxLength(500).IsUnicode();
         // 配置一对多关系
         builder.HasOne<Article>(p => p.ComArticle).WithMany(p => p.ArtComments).HasForeignKey(p => p.ComArticleId);
     }
 }

上述配置定义了 Article 实体和 Comment 实体之间的一对多关系。每个 Comment 实体都有一个外键 ComArticleId,它指向了 Article 实体的主键 Id。

操作一对多关系
接下来,我们来看看如何使用一对多关系进行一些基本的操作。

插入数据
要插入一条包含多个评论的文章,我们可以这样做:

static void InsertData()
{
    using (var myDb = new MyDbContext())
    {
        var article = new Article
        {
            Title = "国足提赢日本了",
            Message = "20年前,xxxxxxxxx\r\nxx",
        };
        var comment = new Comment
        {
            ComMessage = "操,怎么拿20年前的新闻出来糊弄人"
        };

        var comment1 = new Comment
        {
            ComMessage = "标题党"
        };
        article.ArtComments.Add(comment1);
        article.ArtComments.Add(comment);
        myDb.Articles.Add(article);
        myDb.SaveChanges();
    }
}

查询关联表数据
为了获取文章及其相关评论,我们可以使用 Include 方法或者Select 来加载相关的评论数据:

 /// <summary>
 /// 查询关联表数据
 /// 1、可以使用Include();
 /// 2、可以使用Select();
 /// </summary>
 static void SelectData1()
 {
     using (var myDb = new MyDbContext())
     {
         // Include()把关联的表带出来
         // 也可以用Select() 把关联表查询出来
         // var comment = myDb.Comments.Include(p => p.ComArticle).Single(p => p.Id == 1);
         var comment = myDb.Comments.Select(p => new { p.Id, p.ComMessage, artId = p.ComArticle.Id, p.ComArticle.Title, p.ComArticle.Message }).First(p => p.Id == 1);
         Console.WriteLine($"commId={comment.Id};commMsg={comment.ComMessage}");
         Console.WriteLine($"artId={comment.artId};artTitle={comment.Title};artCom={comment.Message}");
         var sql = myDb.Comments.Include(p => p.ComArticle).ToQueryString();
         //               SELECT [t].[Id], [t].[ComArticleId], [t].[ComMessage], [t0].[Id], [t0].[Message], [t0].[Title]
         //                FROM[T_Comment] AS[t]
         //LEFT JOIN[T_Article] AS[t0] ON[t].[ComArticleId] = [t0].[Id]
         Console.WriteLine(sql);

     }
 }

/// <summary>
 /// 单向导航属性 HasOne() .WithMany()(配置一对多,配置在从表配置文件中) = HasMany() .WithOne(配置多对一,配置在主表配置文件中)  关系配置在任意一方都可以
 /// 对于一个表被多个表引用时,建议使用单向导航属性,比如员工信息被系统多个地方使用,就使用单向导航属性
 /// 案例请看 User与Leave实体直接的关联关系,详细配置看LeaveConfig
 /// </summary>
 static void SingleForeignKey()
 {
      using (var mydb = new MyDbContext())
      {
          var user = new User { Name = "小杨同学" };
          var leave = new Leave() { Requester = user, Remark = "请假去玩" };
          mydb.Leaves.Add(leave);
          mydb.SaveChanges();
          var leaveObj = mydb.Leaves.First(p => p.LeaveId == 1);
          Console.WriteLine(leaveObj.LeaveId + ";RequesterName=" + leaveObj.Requester.Name);
      }
  }
 internal class User
 {
     public int UserId { get; set; }

     public string Name { get; set; }
 }
 
 internal class UserConfig : IEntityTypeConfiguration<User>
  {
      public void Configure(EntityTypeBuilder<User> builder)
      {
          builder.ToTable("T_User");
      }
  }
internal class Leave
{
    public int LeaveId { get; set; }

    public User Requester { get; set; } // 请求人
    public User Approver { get; set; } // 审批人
    public string Remark { get; set; }
}

internal class LeaveConfig : IEntityTypeConfiguration<Leave>
 {
     public void Configure(EntityTypeBuilder<Leave> builder)
     {
         builder.ToTable("T_Leave");
         builder.HasOne<User>(p => p.Requester).WithMany().IsRequired();
         builder.HasOne<User>(p => p.Approver).WithMany();
     }
 }
  /// <summary>
  /// 双向导航属性
  /// 对于主从表使用双向导航属性
  /// 比如单据头与单据明细
  /// 案例请看Comment与Article 之间的关联关系 详细配置看CommentConfig 
  /// 给Comment表,冗余外键;避免关联查询消耗性能(双向导航属性)
  /// 外键名称必须与数据库中的ID名称一致,否则会生成列ID字段。
  /// </summary>
  static void MuliHasForeignKey()
  {
      using (var mydb = new MyDbContext())
      {
          var comment = mydb.Comments.Select(p => new { p.ComMessage, p.Id, p.ComArticleId }).Single(p => p.Id == 2);
          Console.WriteLine(comment.Id + ";" + comment.ComArticleId);
      }
  }
// 文章配置类
 public class ArticleConfig : IEntityTypeConfiguration<Article>
 {
     public void Configure(EntityTypeBuilder<Article> builder)
     {
         builder.ToTable("T_Article");
         builder.Property(p => p.Title).IsRequired().IsUnicode().HasMaxLength(100);
         builder.Property(p => p.Message).IsRequired().IsUnicode().HasMaxLength(500);
     }
 }
 // 评论配置类
 internal class CommentConfig : IEntityTypeConfiguration<Comment>
{
    public void Configure(EntityTypeBuilder<Comment> builder)
    {
        builder.ToTable("T_Comment");
        builder.Property(p => p.ComMessage).IsRequired().HasMaxLength(500).IsUnicode();
        // 配置一对多关系
        builder.HasOne<Article>(p => p.ComArticle).WithMany(p => p.ArtComments).HasForeignKey(p => p.ComArticleId);
    }
}
  internal class MyDbContext : DbContext
  {
      public DbSet<Article> Articles { get; set; }
      public DbSet<Comment> Comments { get; set; }
      public DbSet<User> Users { get; set; }
      public DbSet<Leave> Leaves { get; set; }
      protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
      {
          base.OnConfiguring(optionsBuilder);
          string conStr = "Server=.;Database=Demo2;uid=sa;pwd=sasa;TrustServerCertificate=True;";
          optionsBuilder.UseSqlServer(conStr);
          //optionsBuilder.UseLoggerFactory(loggerFactory);
          //optionsBuilder.LogTo(sql =>
          //{
          //    if (!sql.Contains("CommandExecuted")) return;
          //    Console.WriteLine(sql);
          //}); // 简单查询sql日志
      }

      protected override void OnModelCreating(ModelBuilder modelBuilder)
      {
          base.OnModelCreating(modelBuilder);
          //modelBuilder.Entity<User>().ToTable("T_User"); 这样也可以设置表名称
          var assembly = this.GetType().Assembly;
          modelBuilder.ApplyConfigurationsFromAssembly(assembly);
      }
  }
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值