本文章是根據 微軟MVP solenovex(楊旭)老師的視頻教程撰寫而來,再加上自己的一些理解,
視頻教程地址:https://www.bilibili.com/video/BV1xa4y1v7rR
GitHub原始碼:https://github.com/hllive/LearnEFCore3.1
1、簡單修改關系資料
查詢第一個俱樂部并把關聯的聯賽資料也查詢出來,然后修改關聯League中的Name屬性,由于context比較智能,它會追蹤查詢出來的Club以及它關聯的資料,所以League一旦發生變化了之后會生成相應的Update陳述句,
[HttpPut("UpdateLeagueForClub")]
public IActionResult UpdateLeagueForClub()
{
var club = _dbContext.Clubs.Include(c => c.League).FirstOrDefault();
club.League.Name = "新名稱";
int count = _dbContext.SaveChanges();
return Ok(count);
}

2、修改離線關系資料
通過查詢多對多的關系資料,再修改關聯的資料
[HttpPut("UpdatePlayerForGame")]
public IActionResult UpdatePlayerForGame()
{
//1、通過Game查詢出多對多的GamePlayer的Player資料
var game = _dbContext.Games
.Include(g => g.GamePlayers)
.ThenInclude(p => p.Player)
.AsNoTracking()//不追蹤(離線資料)
.FirstOrDefault();
//2、修改Game中第一個隊員的Name屬性
var firstPlayer = game.GamePlayers.First().Player;
firstPlayer.Name = "李曉明";
//3、修改隊員(離線資料需要使用Update()方法)
_dbContext.Players.Update(firstPlayer);
//執行資料庫操作
int count = _dbContext.SaveChanges();
return Ok(count);
}

從執行結果中可以看出,在不追蹤的情況下,使用Context的Update()方法,生成的SQL陳述句會把Game物件和Game物件下的所有隊員都更新一遍,
但是我們只是想修改Game下的第一個隊員的Name屬性,EFCore卻執行了這么多更新操作,顯然這樣是不合理的,
怎么實作只修改一個Player物件呢?
使用Context上有個Entry()方法,然后傳入被修改的物件,Entry上有一個狀態State讓他等于EntityState.Modified;這里就相當于手動設定他的狀態,這個時候呢,它就只會修改一條資料,也就是第一個player資料,其它關聯的資料狀態不會被修改,

[HttpPut("UpdatePlayerForGame")]
public IActionResult UpdatePlayerForGame()
{
//1、通過Game查詢出多對多的GamePlayer的Player資料
var game = _dbContext.Games
.Include(g => g.GamePlayers)
.ThenInclude(p => p.Player)
.AsNoTracking()//不追蹤(離線資料)
.FirstOrDefault();
//2、修改Game中第一個隊員的Name屬性
var firstPlayer = game.GamePlayers.First().Player;
firstPlayer.Name = "李曉明";
//3、修改隊員(離線資料需要使用Update()方法)
//_dbContext.Players.Update(firstPlayer);
//4、使用EntityState.Modified手動設定修改狀態
_dbContext.Entry(firstPlayer).State = EntityState.Modified;
//執行資料庫操作
int count = _dbContext.SaveChanges();
return Ok(count);
}

根據執行結果可以看出只修改了一條記錄
3、修改多對多的資料
1、直接傳值、將比賽和隊員關聯起來
比如想添加隊員和比賽之間的關系,假如前端傳入比賽ID和隊員ID,由于GamePlayer在Context中沒有設定DbSet屬性,所以使用Context.Add(GamePlayer)
[HttpPut("GameAndPlayer")]
public IActionResult UpdateGameAndPlayer()
{
//1、添加隊員和比賽之間的關系
var gamePlayer = new GamePlayer
{
GameId = new Guid("D0E17A1A-6AC5-472E-C1D7-08D848950DDD"),//假如是前端傳入的比賽ID
PlayerId = new Guid("FA896D64-E87C-4087-4E18-08D847725F2B")//假如是前端傳入的隊員ID
};
//2、將GamePlayer添加至Context中
_dbContext.Add(gamePlayer);
//執行資料庫操作
int count = _dbContext.SaveChanges();
return Ok(count);
}
生成的SQL陳述句
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [GamePlayer] ([PlayerId], [GameId])
VALUES (@p0, @p1);
',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='FA896D64-E87C-4087-4E18-08D847725F2B',@p1='D0E17A1A-6AC5-472E-C1D7-08D848950DDD'
2、查詢出比賽,再添加隊員
[HttpPut("GameAddPlayer")]
public IActionResult UpdateGameAddPlayer()
{
//1、把第一個比賽先查出來
var game = _dbContext.Games.FirstOrDefault();
//2、給查出來的比賽中添加一個隊員
game.GamePlayers.Add(new GamePlayer { PlayerId = new Guid("916EA175-5AA9-4249-4E19-08D847725F2B") });//將隊員添加至比賽中
//執行資料庫操作
int count = _dbContext.SaveChanges();
return Ok(count);
}

4、洗掉多對多關系資料
[HttpDelete("GamePlayer")]
public IActionResult DeleteGamePlayer() {
//1、新建一個比賽和隊員直接的關系物件,比如是前段傳入的比賽ID和隊員ID
var gamePlayer = new GamePlayer
{
GameId = new Guid("D0E17A1A-6AC5-472E-C1D7-08D848950DDD"),//假如是前端傳入的比賽ID
PlayerId = new Guid("FA896D64-E87C-4087-4E18-08D847725F2B")//假如是前端傳入的隊員ID
};
//2、在Context中把GamePlayer洗掉
_dbContext.Remove(gamePlayer);
//執行資料庫操作
int count = _dbContext.SaveChanges();
return Ok(count);
}
執行生成的SQL陳述句
exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [GamePlayer]
WHERE [PlayerId] = @p0 AND [GameId] = @p1;
SELECT @@ROWCOUNT;'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='FA896D64-E87C-4087-4E18-08D847725F2B',@p1='D0E17A1A-6AC5-472E-C1D7-08D848950DDD'
5、修改一對一資料
1、修改變化追蹤的資料
[HttpPut("PlayerAddResume")]
public IActionResult UpdatePlayerAddResume() {
//1、查詢出王建國隊員
var player = _dbContext.Players.FirstOrDefault(p => p.Name == "王建國");
//2、給隊員添加簡歷物件
player.Resume = new Resume { Description = "我是一個DotNet工程師" };
//執行資料庫操作
int count = _dbContext.SaveChanges();
return Ok(count);
}
執行生成的SQL陳述句
SELECT TOP(1) [p].[Id], [p].[Birth], [p].[ClubId], [p].[Name], [p].[ResumeId]
FROM [Players] AS [p]
WHERE [p].[Name] = N'王建國'
go
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Resumes] ([Id], [Description], [PlayerId])
VALUES (@p0, @p1, @p2);
',N'@p0 uniqueidentifier,@p1 nvarchar(200),@p2 uniqueidentifier',@p0='4AEE8A24-9803-4823-8737-08D8489C50B1',@p1=N'我是一個DotNet工程師',@p2='FA896D64-E87C-4087-4E18-08D847725F2B'
go
2、修改離線資料
使用Attach方法會判斷player主鍵是否有值,如果有值就是未修改狀態
[HttpPut("PlayerAddResume2")]
public IActionResult UpdatePlayerAddResume2()
{
//1、查詢出王建國隊員(不追蹤)
var player = _dbContext.Players.AsNoTracking().FirstOrDefault(p => p.Name == "李剛");
//2、給隊員添加簡歷物件
player.Resume = new Resume { Description = "我是全堆疊工程師" };
//執行資料庫操作
_dbContext.Attach(player);
int count = _dbContext.SaveChanges();
return Ok(count);
}
這個執行效果和上面實體差不多
3、修改已經存在的資料
如果再次執行以下Action會報錯,報錯原因是李剛的簡歷在資料庫中已經存在,
[HttpPut("PlayerAddResume2")]
public IActionResult UpdatePlayerAddResume2()
{
//1、查詢出王建國隊員(不追蹤)
var player = _dbContext.Players.AsNoTracking().FirstOrDefault(p => p.Name == "李剛");
//2、給隊員添加簡歷物件
player.Resume = new Resume { Description = "我是全堆疊工程師" };
//執行資料庫操作
_dbContext.Attach(player);
int count = _dbContext.SaveChanges();
return Ok(count);
}
分析
查詢出隊員資料中只包括了Players表中的資料,而沒有關聯其他關系資料,也就是沒有關聯Resume,在player物件中Resume為null,在第2句中新new了一個Resume,這時候EFCore認為是給隊員添加簡歷,但是執行資料庫的時候發現已經存在簡歷資料,所以執行失敗
怎么解決這個問題呢?其實就是在查詢隊員的時候一起把Resume也查詢出來(使用Include關聯),如果隊員有簡歷資料,player物件中Resume就不為null,在記憶體中就有變化追蹤player的Resume
[HttpPut("PlayerAddResume3")]
public IActionResult UpdatePlayerAddResume3()
{
//1、查詢出王建國隊員,一起把Resume也查詢出來
var player = _dbContext.Players
.Include(p=>p.Resume).FirstOrDefault(p => p.Name == "李剛");
//2、給隊員添加簡歷物件,如果player中Resume已經存在,會先洗掉之前的Resume在添加新物件
player.Resume = new Resume { Description = "我是全堆疊工程師" };
//執行資料庫操作
int count = _dbContext.SaveChanges();
return Ok(count);
}
執行結果

博客文章可以轉載,但不可以宣告為原創
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/75.html
