EF Core一对多关系

什么是实体间关系

1、“关系数据库”
2、数据库表之间的关系:一对一、一对多、多对多
3、EF Core不仅支持单实体操作,更支持多实体的关系操作。
4、三部:
—— 实体类中关系属性;
——FluentAPI关系配置;
——使用关系操作。

一对多:实体类

1、文章实体类Article、评论实体类Comment。一篇文章对应多条评论。

 class Article
{
    public long Id { get; set; }
    public string Title { get; set; }
    public string Content {  get; set; }
    public List<Comment> Comments { get; set; } = new List<Comment>();
}
 class Comment
{
    public long Id { get; set; }
    public string Message { get; set; }
    // 显式定义外键
    //public long ArticleId { get; set; }
    public Article Article { get; set; }
}

一对多: 关系配置

  1. 一对多:builder.HasOne
    (b => b.Article).WithMany(a => a.Comments)
class CommentConfig : IEntityTypeConfiguration<Comment>
{
    public void Configure(EntityTypeBuilder<Comment> builder)
    {
        builder.ToTable("T_Comments");
        builder.Property(b=>b.Message).IsUnicode(true).IsRequired();
        builder.HasOne<Article>(b => b.Article).WithMany(a => a.Comments).IsRequired();//.HasForeignKey(c => c.ArticleId)

    }
}
  1. 一对一:builder.HasOne
    (b => b.Article).WithOne(a => a.Comments)
  2. 多对多:builder.HasMany
    (b => b.Article).WithMany(a => a.Comments)

一对多关系数据的获取

1、通过主表关联子表

var res = dbContext.Articles.Include(a=>a.Comments).Single(a => a.Id == 1);
Console.WriteLine(res.Title);
Console.WriteLine(res.Content);
foreach (var comment in res.Comments)
{
    Console.WriteLine(comment.Message);
}

2、通过子表关联主表

var res2=dbContext.Comments.Include(a=>a.Article).Single(a=>a.Id == 2);
Console.WriteLine(res2.Message);
Console.WriteLine(res2.Article.Title);
Console.WriteLine(res2.Article.Content);

额外的外键

为什么需要外键属性

1、EF Core 会在数据表中建外键列。
2、如果需要获取外键列的值,就需要做关联查询,效率低。

设置外键属性

1、在实体类中显示声明一个外键属性。

 class Comment
{
    public long Id { get; set; }
    public string Message { get; set; }
    //显式定义外键
    public long ArticleId { get; set; }
    public Article Article { get; set; }
}

2、关系配置中通过HasForeignKey(c=>c.ArticleId)指定这个属性为外键。

class CommentConfig : IEntityTypeConfiguration<Comment>
{
    public void Configure(EntityTypeBuilder<Comment> builder)
    {
        builder.ToTable("T_Comments");
        builder.Property(b=>b.Message).IsUnicode(true).IsRequired();
        builder.HasOne<Article>(b => b.Article).WithMany(a => a.Comments)
            .HasForeignKey(c=>c.ArticleId).IsRequired();//.HasForeignKey(c => c.ArticleId)

    }
}

3、除非必要,否则不用声明,因为会引入重复。

select(b=> new {b.xxx,b.yyy}) 获取指定的列

var res3 = dbContext.Comments.Select(b=> new { b.Id,b.ArticleId}).Single(a=>a.Id == 3);
Console.WriteLine(res3.Id+","+res3.ArticleId);

导航属性

单向导航属性

1、不设置反向属性,配置的时候WithMany()不设置参数即可。

 class User
{
    public long Id { get; set; }
    public string Name { get; set; }
}
 class Leave
{
    public long Id { get; set; }
    public User Requester { get; set; }
    public User Approver { get; set; }
    public string Remarks {  get; set; }
}
class LeaveConfig : IEntityTypeConfiguration<Leave>
{
    public void Configure(EntityTypeBuilder<Leave> builder)
    {
        builder.ToTable("T_Leaves");
        builder.HasOne<User>(b=>b.Approver).WithMany().IsRequired();
        builder.HasOne<User>(b => b.Requester).WithMany();
    }
}

双向导航属性

1、设置反向属性,配置的时候WithMany()设置参数即可。

 class Article
{
    public long Id { get; set; }
    public string Title { get; set; }
    public string Content {  get; set; }
    public List<Comment> Comments { get; set; } = new List<Comment>();
}
 class Comment
{
    public long Id { get; set; }
    public string Message { get; set; }
    //显式定义外键
    public long ArticleId { get; set; }
    public Article Article { get; set; }
}
class CommentConfig : IEntityTypeConfiguration<Comment>
{
    public void Configure(EntityTypeBuilder<Comment> builder)
    {
        builder.ToTable("T_Comments");
        builder.Property(b=>b.Message).IsUnicode(true).IsRequired();
        builder.HasOne<Article>(b => b.Article).WithMany(a => a.Comments)
            .HasForeignKey(c=>c.ArticleId).IsRequired();//.HasForeignKey(c => c.ArticleId)
    }
}

单向导航属性如何反向获取数据

1、通过Include(a=>a.Approver)来获取主表 数据

User u2 = new User { Name="张三" };
User u1 = new User { Name = "雷锋" };
Leave le = new Leave { Remarks="回家",Requester=u1,Approver=u2};
dbContext.Leaves.Add(le);
await dbContext.SaveChangesAsync();
//以上为插入数据,下面为查询数据,通过
var res=dbContext.Leaves.Include(a=>a.Approver).Include(a=>a.Requester).SingleOrDefault(a=>a.Id==3);
if (res != null)
Console.WriteLine(res.Approver.Name+","+res.Requester.Name);

对于主从结构的“一对多”表关系,一般是声明双向导航属性。
对于其他“一对多”表关系:如果表a属于被很多表b、c等引用的基础表,则使用单向导航属性,否则可以自由决定是否使用双向导航属性。

双向导航属性关系配置在任何一方都可以

builder.HasOne<Article>(b => b.Article).WithMany(a => a.Comments)
    .HasForeignKey(c => c.ArticleId).IsRequired();

builder.HasMany(b => b.Comments).WithOne(b => b.Article)
    .HasForeignKey(b=>b.ArticleId).IsRequired();

考虑到有单向导航属性的可能,我们一般采用HasOne().WithMany()

自引用的组织结构树

1、实体类

 class OrgUnit
{
    public long Id { get; set; }
    public string Name { get; set; }
    public OrgUnit? Parent { get; set; }
    public List<OrgUnit> Children { get; set; }=new List<OrgUnit>();
}

2、配置

class OrgUnitConfig : IEntityTypeConfiguration<OrgUnit>
{
    public void Configure(EntityTypeBuilder<OrgUnit> builder)
    {
        builder.ToTable("T_OrgUnits");
        builder.Property(e => e.Name).IsUnicode().IsRequired().HasMaxLength(50);
        builder.HasOne<OrgUnit>(a => a.Parent).WithMany(o => o.Children)
            .OnDelete(DeleteBehavior.NoAction).IsRequired(false);
    }
}

3、插入数据

using(MyDbContext dbContext=new MyDbContext())
{
	 OrgUnit headquarters = new OrgUnit() { Name = "总部" };
	 	
	 OrgUnit china = new OrgUnit() { Name = "中国" };
	 china.Parent = headquarters;
	 headquarters.Children.Add(china);
	 OrgUnit shanghai = new OrgUnit() { Name = "上海分部" };
	 shanghai.Parent = china;
	 china.Children.Add(shanghai);
	 OrgUnit wuhan = new OrgUnit() { Name = "武汉分部" };
	 wuhan.Parent = china;
	 china.Children.Add(wuhan);
	
	 OrgUnit russia = new OrgUnit() { Name = "俄罗斯" };
	 russia.Parent = headquarters;
	 headquarters.Children.Add(russia);
	 OrgUnit mosike = new OrgUnit() { Name = "莫斯科分部" };
	 mosike.Parent = russia;
	 russia.Children.Add(mosike);
	 OrgUnit shengbidebao = new OrgUnit() { Name = "圣彼得堡分部" };
	 shengbidebao.Parent = russia;
	 russia.Children.Add(shengbidebao);
	
	
	 OrgUnit korea = new OrgUnit() { Name = "韩国" };
	 korea.Parent = headquarters;
	 headquarters.Children.Add(korea);
	 OrgUnit shouer = new OrgUnit() { Name = "首尔分部" };
	 shouer.Parent = korea;
	 korea.Children.Add(shouer);
	 OrgUnit fushan = new OrgUnit() { Name = "釜山分部" };
	 fushan.Parent = korea;
	 korea.Children.Add(fushan);
	
	
	 OrgUnit thailand = new OrgUnit() { Name = "泰国" };
	 thailand.Parent = headquarters;
	 headquarters.Children.Add(thailand);
	 OrgUnit mangu = new OrgUnit() { Name = "曼谷分部" };
	 mangu.Parent = thailand;
	 thailand.Children.Add(mangu);
	 OrgUnit batiya = new OrgUnit() { Name = "芭提雅分部" };
	 batiya.Parent = thailand;
	 thailand.Children.Add(batiya);
	
	 dbContext.OrgUnits.Add(headquarters);
	 await dbContext.SaveChangesAsync();
}

4、递归打印结构树

static void PrintTree(int identity, MyDbContext dbContext, OrgUnit parent)
{
    var children = dbContext.OrgUnits.Where(o => o.Parent == parent);
    foreach (var child in children)
    {
        Console.WriteLine(new string('-',identity)+child.Name);
        PrintTree(identity+1,dbContext,child);
    }
}
var root=dbContext.OrgUnits.Single(o=>o.Parent==null);
Console.WriteLine(root.Name);
PrintTree(1, dbContext, root);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值