什么是实体间关系
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; }
}
一对多: 关系配置
- 一对多: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)
}
}
- 一对一:builder.HasOne
(b => b.Article).WithOne(a => a.Comments) - 多对多: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);