在AspNetCore中简单使用EFcore的一对多关系

在AspNetCore中简单使用EFcore的一对多关系

我们将创建一个简单的示例。
准备两个模型:

  1. 待办事务列表(TodoList)
    一个待办事务列表包含多个事务节点。
  2. 事务节点 (TransactionNode)
    每个事务节点属于一个待办事务列表。

待办事务列表(TodoList)模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace TestEFc_IEQable_AspNetCore01x02
{
public class ToDoList
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]// 关闭自动生成主键,使用手动赋值
[MaxLength(50)]
public string? Id { get; set; }
public string? ListTitle { get; set; }
public string? ListDescription { get; set; }
public string? State { get; set; }
public List<TransactionNode>? TransactionNodes { get; set; }//导航属性,用于访问与此待办事务列表相关联的事务节点
}
}

事务节点(TransactionNode)模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace TestEFc_IEQable_AspNetCore01x02
{
public class TransactionNode
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[MaxLength(50)]
public string? Id { get; set; }
public string? SerialNumber { get; set; }
public ToDoList? ToDoList { get; set; }//导航属性,用于访问与此事务节点相关联的待办事务列表, 有时也叫引用类型
public string? State { get; set; }//0:未完成 1:完成
public string? Content { get; set; }
}
}

配置数据库上下文(DbContext):

1
2
3
4
5
6
7
8
9
10
11
12
using Microsoft.EntityFrameworkCore;
namespace TestEFc_IEQable_AspNetCore01x02
{
public class DateContext:DbContext
{
public DateContext(DbContextOptions<DateContext> options) : base(options) { }

public DbSet<ToDoList> ToDoListTable { get; set; }
public DbSet<TransactionNode> TransactionNodeTable { get; set; }
}
}

为模型类进行配置:

TodoList和TransactionNode之间的一对多关系可以通过导航属性进行配置。EF Core会自动识别这种关系。
ToDoList:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Microsoft.EntityFrameworkCore;

namespace TestEFc_IEQable_AspNetCore01x02
{
public class ToDoListConfig: IEntityTypeConfiguration<ToDoList>
{
public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<ToDoList> builder)
{
builder.ToTable("ToDoListTable");
builder.Property(a => a.ListTitle).HasMaxLength(50);
builder.Property(a => a.ListDescription).HasMaxLength(250);
}
}
}

TransactionNode:
注意这里的HasOne和WithMany的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Microsoft.EntityFrameworkCore;
namespace TestEFc_IEQable_AspNetCore01x02
{
public class TransactionNodeConfig:IEntityTypeConfiguration<TransactionNode>
{
public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<TransactionNode> builder)
{
builder.ToTable("TransactionNodeTable");
//重点看这里的一对多关系配置,注意HasOne和WithMany的使用
builder.HasOne<ToDoList>(a => a.ToDoList).WithMany(c => c.TransactionNodes).IsRequired();
builder.Property(a => a.Content).HasMaxLength(250);
}
}
}

program.cs中配置DbContext:

1
2
3
builder.Services.AddDbContext<DateContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
);

在控制器中使用:

添加数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[HttpGet]
public async Task<ActionResult<string>> Test01_AddData()
{
var toDolist01 = new ToDoList()
{
Id = await _idGenService.NewGuidAsync(3),
ListTitle = "List02",
ListDescription = "List02_Description",
State = "0",
//这里需要初始化TransactionNodes,不然后面 toDolist01.TransactionNodes.Add会报错
//, if (toDolist01 == null)拦不住transactionNodes不为null,因为toDolist01不为null,只是TransactionNodes为null而已
//所以要判断TransactionNodes是否为null,而不是toDolist01是否为null
TransactionNodes = new List<TransactionNode>()
};

//if (toDolist01 == null)
//{
// return await Task.FromResult("toDolist01 is null");
//}

if (toDolist01.TransactionNodes == null)
{
return await Task.FromResult("toDolist01.TransactionNodes is null");
}
var transactionNode01 = new TransactionNode()
{
Id = await _idGenService.NewGuidAsync(3),
SerialNumber = "01",
State = "0",
Content = "Content02x01",
};
var transactionNode02 = new TransactionNode()
{
Id = await _idGenService.NewGuidAsync(3),
SerialNumber = "02",
State = "0",
Content = "Content02x02",
};
var transactionNode03 = new TransactionNode()
{
Id = await _idGenService.NewGuidAsync(3),
SerialNumber = "03",
State = "0",
Content = "Content02x03",
};
toDolist01.TransactionNodes.Add(transactionNode01);
toDolist01.TransactionNodes.Add(transactionNode02);
toDolist01.TransactionNodes.Add(transactionNode03);
await _dateContext.ToDoListTable.AddAsync(toDolist01);
await _dateContext.SaveChangesAsync();
return await Task.FromResult("ok");
}

添加数据其实只要注意一下要初始化导航属性的集合就行了,比如上面的TransactionNodes。

查询数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[HttpGet]
public async Task<ActionResult<string>> Test01_GetData()
{
//firstOrDefaultAsync是链式调用IQueryable

//注意这里
// 把子表一并查出,没加 .Include(t => t.TransactionNodes),导航属性,懒加载不会触发
var toDoList = await _dateContext.ToDoListTable
.Include(t => t.TransactionNodes)
.FirstOrDefaultAsync(x => x.Id == "84c");
//如果不加Include,导航属性TransactionNodes为null,但就算是这样if (toDoList == null)也不会触发原因同上,

//因为toDoList不为null,还是会通过
//if (toDoList == null)
//{
// Console.WriteLine("toDoList is null");
// return await Task.FromResult("toDoList is null");
//}
if (toDoList.TransactionNodes == null)
{
Console.WriteLine("toDoList.TransactionNodes is null");
return await Task.FromResult("toDoList.TransactionNodes is null");
}
_logger.LogInformation($"ToDoList Id:{toDoList.Id}, Title:{toDoList.ListTitle}, Description:{toDoList.ListDescription}, State:{toDoList.State}");

foreach (var item in toDoList.TransactionNodes)
{
_logger.LogInformation($"TransactionNode Id:{item.Id}, SerialNumber:{item.SerialNumber}, Content:{item.Content}, State:{item.State}");
}
return await Task.FromResult("ok");
}

在查询数据时,注意使用.Include方法来加载导航属性,以确保相关的事务节点也被加载。

查询数据运行截图:
iusdbf