ASP.NET Core 3.1系列(21)——EFCore中的更新实体操作

32 篇文章 43 订阅

1、前言

前面的博客已经介绍过EFCore中关于新增和删除实体的相关操作,本文开始介绍EFCore中的更新实体操作。与新增实体和删除实体相比,更新实体的操作略微有些复杂,如果在代码的写法上不多加注意,那就很有可能会在后台生成效率低下的SQL,下面开始介绍。

2、构建测试数据库

还是与之前一样,在SQL Server中创建一个数据库Dao,然后创建一张Author数据表,代码如下:

USE [Dao]
GO

/****** Object:  Table [dbo].[Author]    Script Date: 2022/12/16 8:55:33 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Author](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[Name] [nvarchar](20) NULL,
	[Gender] [nvarchar](10) NULL,
	[Age] [int] NULL,
	[Email] [nvarchar](30) NULL,
 CONSTRAINT [PK_Author] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

Author表数据如下所示:

IdNameGenderAgeEmail
1张三3511111111@qq.com
2李四4022222222@qq.com
3王五3733333333@qq.com

最终生成的实体类和数据库上下文代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

// Code scaffolded by EF Core assumes nullable reference types (NRTs) are not used or disabled.
// If you have enabled NRTs for your project, then un-comment the following line:
// #nullable disable

namespace App.Models
{
    public partial class Author
    {
        [Key]
        public int Id { get; set; }
        [StringLength(20)]
        public string Name { get; set; }
        [StringLength(10)]
        public string Gender { get; set; }
        public int? Age { get; set; }
        [StringLength(30)]
        public string Email { get; set; }
    }
}
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using App.Models;

// Code scaffolded by EF Core assumes nullable reference types (NRTs) are not used or disabled.
// If you have enabled NRTs for your project, then un-comment the following line:
// #nullable disable

namespace App.Context
{
    public partial class DaoDbContext : DbContext
    {
        public DaoDbContext()
        {
        }

        public DaoDbContext(DbContextOptions<DaoDbContext> options)
            : base(options)
        {
        }

        public virtual DbSet<Author> Author { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlServer("Data Source=rt-dongshenfeng;Initial Catalog=Dao;User ID=sa;Password=gis1a6b7c!Z;");
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            OnModelCreatingPartial(modelBuilder);
        }

        partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
    }
}

3、更新实体——修改实体属性

一般可以通过修改实体属性的方式实现数据的更新,下面代码演示了将Name='张三'的实体年龄增加10岁:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using System.Linq;

namespace App.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthorController : ControllerBase
    {
        protected readonly DaoDbContext _dbContext;

        public AuthorController(DaoDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public ActionResult<int> Update()
        {
            Author author = _dbContext.Set<Author>().Where(p => p.Name == "张三").FirstOrDefault();
            if (author != null)
            {
                author.Age += 10;
                return _dbContext.SaveChanges();
            }
            return 0;
        }
    }
}

后台一共生成2SQL,可以发现update语句只对Age字段进行更新。

SELECT TOP(1) [a].[Id], [a].[Age], [a].[Email], [a].[Gender], [a].[Name]
FROM [Author] AS [a]
WHERE [a].[Name] = N'张三'
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Author] SET [Age] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 int',@p1=1,@p0=45

如果希望实现批量更新,只需要在foreach循环中修改实体属性即可,下面代码演示了将Author表中的每条记录年龄增加10岁:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;

namespace App.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthorController : ControllerBase
    {
        protected readonly DaoDbContext _dbContext;

        public AuthorController(DaoDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public ActionResult<int> Update()
        {
            List<Author> authors = _dbContext.Set<Author>().ToList();
            foreach (Author author in authors)
            {
                author.Age += 10;
            }
            return _dbContext.SaveChanges();
        }
    }
}

4、更新实体——Update、UpdateRange

EFCore提供的UpdateUpdateRange方法也可以实现数据的更新,前者用于更新单个实体,后者可以实现批量更新实体,先来看一段示例代码:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using System.Linq;

namespace App.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthorController : ControllerBase
    {
        protected readonly DaoDbContext _dbContext;

        public AuthorController(DaoDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public ActionResult<int> Update()
        {
            Author author = _dbContext.Set<Author>().Where(p => p.Name == "张三").FirstOrDefault();
            if (author != null)
            {
                author.Age += 10;
                _dbContext.Update(author);
                return _dbContext.SaveChanges();
            }
            return 0;
        }
    }
}

与直接修改实体属性相比,这里调用了_dbContext.Update(author)强制更新,下面来看看后台生成的SQL

SELECT TOP(1) [a].[Id], [a].[Age], [a].[Email], [a].[Gender], [a].[Name]
FROM [Author] AS [a]
WHERE [a].[Name] = N'张三'
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Author] SET [Age] = @p0, [Email] = @p1, [Gender] = @p2, [Name] = @p3
WHERE [Id] = @p4;
SELECT @@ROWCOUNT;

',N'@p4 int,@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p4=1,@p0=55,@p1=N'11111111@qq.com',@p2=N'男',@p3=N'张三'

可以发现,后台的update语句不仅将Age字段更新了,同时也把其他字段都更新了。因此UpdateUpdateRange方法最好慎用,因为它们每次都会强制更新全部字段。这里你可能还有一个疑问:既然获取到实体后直接更新属性就能实现更新操作,那为什么还要提供这两个方法?其实上面的代码属于先查询、后更新,因此后台会生成2SQL,其中一条是select语句,另一条则是update语句。而UpdateUpdateRange方法允许你跳过查询阶段,直接创建实体实现数据更新,当然前提是你创建的实体必须包含主键,看下面一段代码:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;

namespace App.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthorController : ControllerBase
    {
        protected readonly DaoDbContext _dbContext;

        public AuthorController(DaoDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public ActionResult<int> Update()
        {
            Author author = new Author
            {
                Id = 1,
                Name = "张三",
                Age = 35
            };
            _dbContext.Update(author);
            return _dbContext.SaveChanges();
        }
    }
}

如果采用这种写法,后台只会生成1SQL

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Author] SET [Age] = @p0, [Email] = @p1, [Gender] = @p2, [Name] = @p3
WHERE [Id] = @p4;
SELECT @@ROWCOUNT;

',N'@p4 int,@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p4=1,@p0=35,@p1=NULL,@p2=NULL,@p3=N'张三'

查看一下运行结果,发现一个新的问题:GenderEmail字段变为NULL了?

在这里插入图片描述
在上面的代码中,我们创建的实体只显示声明了Id、Name、Age的字段值,而GenderEmail未显示声明,因此Update方法会默认将这两个字段赋值为NULL,解决方法也很简单,声明所有字段即可:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;

namespace App.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthorController : ControllerBase
    {
        protected readonly DaoDbContext _dbContext;

        public AuthorController(DaoDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public ActionResult<int> Update()
        {
            Author author = new Author
            {
                Id = 1,
                Name = "张三",
                Gender = "男",
                Age = 35,
                Email = "11111111@qq.com"
            };
            _dbContext.Update(author);
            return _dbContext.SaveChanges();
        }
    }
}

此时后台生成的SQL如下:

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Author] SET [Age] = @p0, [Email] = @p1, [Gender] = @p2, [Name] = @p3
WHERE [Id] = @p4;
SELECT @@ROWCOUNT;

',N'@p4 int,@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p4=1,@p0=35,@p1=N'11111111@qq.com',@p2=N'男',@p3=N'张三'

5、更新实体——Entry

Entry方法与Update方法类似,它会对全部字段进行更新,来看下面一段代码:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace App.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthorController : ControllerBase
    {
        protected readonly DaoDbContext _dbContext;

        public AuthorController(DaoDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public ActionResult<int> Update()
        {
            Author author = _dbContext.Set<Author>().Where(p => p.Name == "张三").FirstOrDefault();
            if (author != null)
            {
                author.Age += 10;
                _dbContext.Entry(author).State = EntityState.Modified;
                return _dbContext.SaveChanges();
            }
            return 0;
        }
    }
}

后台共生成2SQL

SELECT TOP(1) [a].[Id], [a].[Age], [a].[Email], [a].[Gender], [a].[Name]
FROM [Author] AS [a]
WHERE [a].[Name] = N'张三'
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Author] SET [Age] = @p0, [Email] = @p1, [Gender] = @p2, [Name] = @p3
WHERE [Id] = @p4;
SELECT @@ROWCOUNT;

',N'@p4 int,@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p4=1,@p0=45,@p1=N'11111111@qq.com',@p2=N'男',@p3=N'张三'

Update类似,Entry也支持直接创建实体更新的操作,但前提是实体必须包含主键,同时创建实体时未被显示赋值的字段将会被更新为NULL

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace App.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthorController : ControllerBase
    {
        protected readonly DaoDbContext _dbContext;

        public AuthorController(DaoDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public ActionResult<int> Update()
        {
            Author author = new Author
            {
                Id = 1,
                Name = "张三",
                Gender = "男",
                Age = 35
            };
            _dbContext.Entry(author).State = EntityState.Modified;
            return _dbContext.SaveChanges();
        }
    }
}

后台生成的SQL如下,由于上面的代码未对Email字段进行显示赋值,因此该字段被更新为NULL

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Author] SET [Age] = @p0, [Email] = @p1, [Gender] = @p2, [Name] = @p3
WHERE [Id] = @p4;
SELECT @@ROWCOUNT;

',N'@p4 int,@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p4=1,@p0=35,@p1=NULL,@p2=N'男',@p3=N'张三'

在这里插入图片描述
如果只希望执行一次SQL,又不希望显示对每个字段都进行赋值,那么可以使用Property方法结合Entry实现部分字段的更新操作,代码如下:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;

namespace App.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthorController : ControllerBase
    {
        protected readonly DaoDbContext _dbContext;

        public AuthorController(DaoDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public ActionResult<int> Update()
        {
            Author author = new Author
            {
                Id = 1,
                Gender = "女"
            };
            _dbContext.Entry(author).Property("Gender").IsModified = true;
            return _dbContext.SaveChanges();
        }
    }
}

后台生成的SQL如下所示:

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Author] SET [Gender] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 nvarchar(10)',@p1=1,@p0=N'女'

6、结语

EFCore中的更新操作略微有些复杂,正确理解每种更新操作生成的SQL是把控程序效率的关键。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值