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
表数据如下所示:
Id | Name | Gender | Age | |
---|---|---|---|---|
1 | 张三 | 男 | 35 | 11111111@qq.com |
2 | 李四 | 女 | 40 | 22222222@qq.com |
3 | 王五 | 男 | 37 | 33333333@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;
}
}
}
后台一共生成2
条SQL
,可以发现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
提供的Update
和UpdateRange
方法也可以实现数据的更新,前者用于更新单个实体,后者可以实现批量更新实体,先来看一段示例代码:
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
字段更新了,同时也把其他字段都更新了。因此Update
或UpdateRange
方法最好慎用,因为它们每次都会强制更新全部字段。这里你可能还有一个疑问:既然获取到实体后直接更新属性就能实现更新操作,那为什么还要提供这两个方法
?其实上面的代码属于先查询、后更新,因此后台会生成2
条SQL
,其中一条是select
语句,另一条则是update
语句。而Update
和UpdateRange
方法允许你跳过查询阶段,直接创建实体实现数据更新,当然前提是你创建的实体必须包含主键,看下面一段代码:
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();
}
}
}
如果采用这种写法,后台只会生成1
条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=NULL,@p2=NULL,@p3=N'张三'
查看一下运行结果,发现一个新的问题:Gender
和Email
字段变为NULL
了?
在上面的代码中,我们创建的实体只显示声明了Id、Name、Age
的字段值,而Gender
和Email
未显示声明,因此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;
}
}
}
后台共生成2
条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=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
是把控程序效率的关键。