0%

EntityFramework Core2——Model属性设置、Model的关系设置、日志、增删改查

设定属性的约束

添加依赖

1
2
3
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup>

在Model中添加约束

1
2
3
4
5
6
7
8
9
public class League
{
public int Id { get; set; }
[Required]//必添的属性
[MaxLength(100)]//最大长度为100
public string Name { get; set; }
[Required, MaxLength(50)]
public string Country { get; set; }
}
  • 然后跟上一讲一样添加迁移,再应用到数据库中,这样就会将对Model的约束应用到数据库中

一对一、一对多、多对多

  • 在EFCore中体现Model之间的关系有两种方式:
    1. 使用导航属性
    2. 在DbContext中配置

使用导航属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Club
{
public Club()
{
Players = new List<Player>();
}
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
//设置该属性在数据库中的类型为date
[Column(TypeName = "date")]
public DateTime DateOfEstablishment { get; set; }
public string History { get; set; }
//导航属性,League跟Club属于一对多关系
public League League { get; set; }
//导航属性,Club跟Player属于一对多关系
public List<Player> Players { get; set; }
}
  • 每一个Club都有一个League属性,因为League属性有可能是指向同一个引用,所以它跟League是属于一对多关系,当EFCore在添加迁移时,它会扫描所有的Model,然后根据导航属性去生成Model之间的关系。

在DbContext中配置

  • 新创建一个比赛类Game来模拟多对多关系,其中Game和Player是属于多对多关系。在这两个Model之间需要一个关系实体GamePlay。

  • 再新建一个简历类Resume来模拟一对一关系

    Snipaste_2020-10-06_20-57-13

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
//Game
public class Game
{
public Game()
{
GamePlayers = new List<GamePlayer>();
}
public int Id { get; set; }
public int Round { get; set; }
//类型后面加上?则表示了该属性为可空属性,对应于数据库就是null,而没加?的就是not null
public DateTimeOffset? StartTime { get; set; }
//导航属性
public List<GamePlayer> GamePlayers { get; set; }
}
//Player
public class Player
{
public Player()
{
GamePlayers = new List<GamePlayer>();
}
public int Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
//导航属性
public List<GamePlayer> GamePlayers { get; set; }
//外键
public int ResumeId { get; set; }
//导航属性
public Resume Resume { get; set; }
}
//GamePlayer
public class GamePlayer
{
public int PlayerId { get; set; }
public int GameId { get; set; }
//导航属性
public Game Game { get; set; }
//导航属性
public Player Player { get; set; }
}
//Resume
public class Resume
{
public int Id { get; set; }
public string Description { get; set; }
//外键
public int PlayId { get; set; }
//导航属性
public Player Player { get; set; }
}
  • 在DbContext有一个OnModelCreating方法,重写它可以对Model进行一些配置,这种配置包括:对属性的约束、对属性类型的修改、添加种子数据等等。如果不在OnModelCreating方法中指定一对一的主体,那么EFCore就会随机选择一个作为主体,所以你可以再OnModelCreating方法中进行指定,这里的主体为Resume,它有一个PlayId的外键。
1
2
3
4
5
6
7
8
9
10
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//联合主键
modelBuilder.Entity<GamePlayer>().HasKey(x => new { x.PlayerId, x.GameId });
//Player和Resume的一对一关系,这样就把Resume中的PlayId指定为外键
modelBuilder.Entity<Resume>()
.HasOne(x => x.Player)
.WithOne(x => x.Resume)
.HasForeignKey<Resume>(x => x.PlayId);
}
  • 然后添加迁移、应用到数据库

添加日志支持

添加依赖

  • 在Linq.Data中添加控制台日志的依赖
1
2
3
4
5
6
7
8
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.8" />
</ItemGroup>
  • 然后在DbContext中创建一个日志工厂
1
2
3
4
5
6
7
8
9
10
11
12
//日志工厂,添加输出Sql语句的支持
public static readonly ILoggerFactory ConsoleLoggerFactory =
LoggerFactory.Create(builder =>
{
//添加过滤,category表示输出日志的种类,这里指执行的Sql语句
//level表示日志的级别
builder.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name
&& level == LogLevel.Information)
//表示输出到控制台
.AddConsole();
});
  • 最后在OnConfiguring方法中用UseLoggerFactory()来注册日志工厂
1
2
3
4
5
6
7
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseLoggerFactory(ConsoleLoggerFactory)
.UseSqlServer(
"Data Source=(localdb)\\MSSQLLocalDB; Initial Catalog=LinqDb");
}

插入数据

  • 当执行完context.Leagues.Add后,数据并没有真正地插入到数据库中,只有执行了context.SaveChanges()这条语句时,数据才更新到了数据库中。
1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main(string[] args)
{
//获取DbContext对象,该对象需要手动Disposable,可以使用C#8的新语法using
using var context = new LinqDbContext();
var league = new League
{
Country = "Italy",
Name = "Serie A"
};
context.Leagues.Add(league);
var count = context.SaveChanges();
Console.WriteLine(count);
}
  • 运行后的结果:

    Snipaste_2020-10-06_22-31-56

  • AddRange:一次可以插入多条数据

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
static void Main(string[] args)
{
//获取DbContext对象,该对象需要手动Disposable,可以使用C#8的新语法
using var context = new LinqDbContext();
var serieA = context.Leagues.Single(x=>x.Name=="Serie A");
var league1 = new League
{
Country = "Italy",
Name = "Serie B"
};
var league2 = new League
{
Country = "Italy",
Name = "Serie C"
};
var club = new Club
{
Name = "AC Milan",
City = "Milan",
DateOfEstablishment = new DateTime(1899, 12, 16),
League = serieA
};
//使用context.xxx.AddRange可以添加指定类型的Model
//也可使用context.AddRange可以添加不同类型的Model
context.AddRange(league1, league2, club);
var count = context.SaveChanges();
Console.WriteLine(count);
}
  • 运行后的结果:

    Snipaste_2020-10-06_22-43-13

查询数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void Main(string[] args)
{
//获取DbContext对象,该对象需要手动Disposable,可以使用C#8的新语法
using var context = new LinqDbContext();
var italy = "Italy";
//当context遇到ToList或者for循环就会触发查询
var leagues = context.Leagues
.Where(x => x.Country == italy)
.ToList();

foreach (var league in leagues)
{
Console.WriteLine(league.Name);
}

//使用foreach循环和ToList的区别是:使用ToList是遇到ToList时,数据库连接才会打开
//而使用foreach时,当开始进行遍历的时候,连接就打开了,直到循环结束,连接才断开
foreach (var league in context.Leagues)
{
Console.WriteLine(league.Name);
}
}
  • 在这里需要注意:

    1. ToList和foreach的区别,推荐使用ToList后再遍历集合
    2. 在查询leagues时,Where条件中使用的是一个变量,当执行Sql时,EFCore就会为其创建一个变量,而如果使用一个固定值”Italy”的话,EFCore会直接把固定值插到条件中。
  • 运行结果:

    Snipaste_2020-10-06_22-58-07

  • 在DbContext中在UseLoggerFactory后面添加.EnableSensitiveDataLogging()会使输出的Sql中参数的值给输出。

模糊查询

  • 这里有两种写法:
    1. 使用Contains
    2. 使用EF提供的Like方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void Main(string[] args)
{
//获取DbContext对象,该对象需要手动Disposable,可以使用C#8的新语法
using var context = new LinqDbContext();
var leagues1 = context.Leagues
.Where(x=>x.Country.Contains("a"))
.ToList();
var leagues2 = context.Leagues
.Where(x => EF.Functions.Like(x.Country, "%a%"))
.ToList();
foreach (var league in leagues1)
{
Console.WriteLine(league.Name);
}
foreach (var league in leagues2)
{
Console.WriteLine(league.Name);
}
}
  • 运行结果:

    Snipaste_2020-10-06_23-07-26

更多常见的有关查询的方法

  • First()、FirstOrDefault():返回第一个。
  • Single()、SingleOrDefault():返回单个,如果Model多于一个,就会抛出异常。
  • Last() LastOrDefault():返回最后一个,使用这个方法必须对集合进行排序。
  • Count()、LongCount()、Min()、Max()、Average()、Sum()
  • Find():如果可以在内存中找到Model,则直接在内存中读取数据。
  • 这些方法都有它们对应的异步版本,就是在方法名后面加上Async。
1
2
3
4
5
6
var single = context.Leagues
.SingleOrDefault(x => x.Id == 2);
//由于前面使用Single已经把数据读到了内存中,执行Find时,它是从内存在拿数据,所以只执行了一条sql语句
var find = context.Leagues.Find(2);
Console.WriteLine(single.Name);
Console.WriteLine(find.Name);
  • 运行结果:

    Snipaste_2020-10-06_23-22-03

1
2
3
4
5
var last = context.Leagues
//倒序
.OrderByDescending(x=>x.Id)
.LastOrDefault(x=>x.Name.Contains("e"));
Console.WriteLine(last.Name);

删除数据

1
2
3
4
5
6
7
8
9
10
//EFCore只能删除被追踪的数据
var milan = context.Clubs.Single(x=>x.Name=="AC Milan");
//删除
context.Remove(milan);
// context.Clubs.Remove(milan);
// context.RemoveRange(milan, milan);
// context.Clubs.RemoveRange(milan, milan);
//只用当遇到SaveChanges,这笔数据才会被删除
var count = context.SaveChanges();
Console.WriteLine(count);
  • 运行结果:

    Snipaste_2020-10-06_23-37-41

修改数据

  • 由于EFCore会自动地追踪我们创建的Model,所以当我们在进行增删改时,EFCore会帮我们把修改的情况给暂存,直到context执行SaveChanges时,EFCore才会把改动应用到数据库中
1
2
3
4
5
6
var league = context.Leagues.First();
//修改
league.Name += "~~";
//保存修改到数据库
var count = context.SaveChanges();
Console.WriteLine(count);
  • 运行结果:

    Snipaste_2020-10-06_23-39-00

删除追踪和附加追踪

1
2
3
4
5
6
7
8
9
//当执行了AsNoTracking方法后EFCore就不会对league对象进行追踪
//如果现在执行SaveChanges方法,修改并不会应用到数据库
var league = context.Leagues.AsNoTracking().First();
league.Name += "++";
//当使用Update方法时,EFCore会重新地对league附加追踪,
//这时执行SaveChanges方法,修改会应用到数据库
context.Leagues.Update(league);
var count = context.SaveChanges();
Console.WriteLine(count);
  • 运行结果:

    Snipaste_2020-10-06_23-46-29

  • 也可以在Context中全局设置删除追踪,在构造器中加上这一句ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;,这个功能一般不用,因为我们还是需要EFCore为我们追踪Model。

1
2
3
4
public LinqDbContext()
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}

学习资料:B站杨旭

-------------本文结束感谢您的阅读-------------