• 9、Entity Framework Core 3.1入门教程-修改关系数据


    本文章是根据 微软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);
    }
    

    执行结果

    博客文章可以转载,但不可以声明为原创

  • 相关阅读:
    libevent中的bufferevent原理
    libevent中的事件机制
    libevent中数据缓冲区buffer分析
    libevent中最小堆实现算法解析
    我眼中的WebViewJavascriptBridge(图解)
    Tinyhttpd精读解析
    app微信支付的集成步骤
    java工厂模式的测试
    java Annotation 注解的使用
    android 连接蓝牙打印机 BluetoothAdapter
  • 原文地址:https://www.cnblogs.com/hllive/p/13557305.html
Copyright © 2020-2023  润新知