1、前言
前面介绍了EFCore
中关于查询和执行原生SQL
的操作,这篇博客就来介绍一下EFCore
中添加实体的相关操作。关于添加实体,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、添加单个实体
如果是添加单个实体,可以使用EFCore
中的Add
方法。Add
方法很好理解,就是在DbSet
中添加一个实体,最后调用SaveChanges
保存即可。代码如下所示:
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> Add()
{
Author author = new Author
{
Name = "赵六",
Gender = "女",
Age = 42,
Email = "44444444@qq.com"
};
_dbContext.Set<Author>().Add(author);
return _dbContext.SaveChanges();
}
}
}
除了使用Add
方法,EFCore
中也可以使用Entry
方法将实体的状态设置为Added
,从而实现添加实体的功能,代码如下所示:
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> Add()
{
Author author = new Author
{
Name = "赵六",
Gender = "女",
Age = 42,
Email = "44444444@qq.com"
};
_dbContext.Entry(author).State = EntityState.Added;
return _dbContext.SaveChanges();
}
}
}
这两种方法都可以实现添加单个实体的功能,它们最终生成的SQL
如下所示:
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Author] ([Age], [Email], [Gender], [Name])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [Author]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p0=42,@p1=N'44444444@qq.com',@p2=N'女',@p3=N'赵六'
4、批量添加实体
既然Add
和Entry
方法可以实现添加单个实体的功能,那我们自然会想到在foreach
循环中去调用它们,从而实现批量添加实体的功能,下面的代码演示了利用Add
结合foreach
实现批量添加实体的功能:
using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
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> Add()
{
List<Author> authors = new List<Author>()
{
new Author
{
Name = "周一",
Gender = "男",
Age = 25,
Email = "55555555@qq.com"
},
new Author
{
Name = "周二",
Gender = "女",
Age = 26,
Email = "66666666@qq.com"
},
new Author
{
Name = "周三",
Gender = "男",
Age = 27,
Email = "77777777@qq.com"
}
};
foreach (Author author in authors)
{
_dbContext.Set<Author>().Add(author);
}
return _dbContext.SaveChanges();
}
}
}
也可以利用Entry
方法结合foreach
实现批量添加实体,代码如下:
using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
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> Add()
{
List<Author> authors = new List<Author>()
{
new Author
{
Name = "周一",
Gender = "男",
Age = 25,
Email = "55555555@qq.com"
},
new Author
{
Name = "周二",
Gender = "女",
Age = 26,
Email = "66666666@qq.com"
},
new Author
{
Name = "周三",
Gender = "男",
Age = 27,
Email = "77777777@qq.com"
}
};
foreach (Author author in authors)
{
_dbContext.Entry(author).State = EntityState.Added;
}
return _dbContext.SaveChanges();
}
}
}
EFCore
中也提供了AddRange
方法用来实现批量添加实体,代码如下:
using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
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> Add()
{
List<Author> authors = new List<Author>()
{
new Author
{
Name = "周一",
Gender = "男",
Age = 25,
Email = "55555555@qq.com"
},
new Author
{
Name = "周二",
Gender = "女",
Age = 26,
Email = "66666666@qq.com"
},
new Author
{
Name = "周三",
Gender = "男",
Age = 27,
Email = "77777777@qq.com"
}
};
_dbContext.Set<Author>().AddRange(authors);
return _dbContext.SaveChanges();
}
}
}
这三种方法都可以实现批量添加实体的功能,它们都会在后台生成3
条SQL
:
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Author] ([Age], [Email], [Gender], [Name])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [Author]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p0=25,@p1=N'55555555@qq.com',@p2=N'男',@p3=N'周一'
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Author] ([Age], [Email], [Gender], [Name])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [Author]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p0=26,@p1=N'66666666@qq.com',@p2=N'女',@p3=N'周二'
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Author] ([Age], [Email], [Gender], [Name])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [Author]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p0=27,@p1=N'77777777@qq.com',@p2=N'男',@p3=N'周三'
5、使用事务添加实体
我们也可以通过开启一个事务的方式来实现添加实体的功能,代码如下:
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> Add()
{
using (var transaction = _dbContext.Database.BeginTransaction())
{
try
{
_dbContext.Set<Author>().Add(new Author
{
Name = "周一",
Gender = "男",
Age = 25,
Email = "55555555@qq.com"
});
_dbContext.Set<Author>().Add(new Author
{
Name = "周二",
Gender = "女",
Age = 26,
Email = "66666666@qq.com"
});
_dbContext.Set<Author>().Add(new Author
{
Name = "周三",
Gender = "男",
Age = 27,
Email = "77777777@qq.com"
});
_dbContext.SaveChanges();
_dbContext.Database.CommitTransaction();
return 1;
}
catch
{
_dbContext.Database.RollbackTransaction();
return 0;
}
}
}
}
}
与上面批量添加实体的结果一样,后台会生成3
条SQL
:
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Author] ([Age], [Email], [Gender], [Name])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [Author]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p0=25,@p1=N'55555555@qq.com',@p2=N'男',@p3=N'周一'
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Author] ([Age], [Email], [Gender], [Name])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [Author]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p0=26,@p1=N'66666666@qq.com',@p2=N'女',@p3=N'周二'
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Author] ([Age], [Email], [Gender], [Name])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [Author]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 int,@p1 nvarchar(30),@p2 nvarchar(10),@p3 nvarchar(20)',@p0=27,@p1=N'77777777@qq.com',@p2=N'男',@p3=N'周三'
6、SqlBulkCopy添加实体
在实际开发过程中,有时可能需要一次导入几万甚至几十万的数据,这时候如果通过Add
结合foreach
批量添加,代码的效率会变得非常低。如果是调用AddRange
方法,那么每次导入的实体数量也是有限制的,超过这个限制就会导致程序报错。这时我们就可以考虑使用SqlBulkCopy
来实现。代码如下所示:
using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Data;
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> Add()
{
// 模拟一个Author集合
List<Author> authors = new List<Author>();
Random random = new Random();
for (int i = 1; i < 10000; i++)
{
authors.Add(new Author
{
Name = "Author_" + i.ToString(),
Gender = i % 2 == 0 ? "男" : "女",
Age = random.Next(20, 50),
Email = "00000000@qq.com"
});
}
// 转换为DataTable
DataTable table = new DataTable();
table.Columns.Add("Id", typeof(int));
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Gender", typeof(string));
table.Columns.Add("Age", typeof(int?));
table.Columns.Add("Email", typeof(string));
foreach (Author author in authors)
{
DataRow row = table.NewRow();
row[1] = author.Name;
row[2] = author.Gender;
row[3] = author.Age;
row[4] = author.Email;
table.Rows.Add(row);
}
// 写入数据库
string connectionString = _dbContext.Database.GetDbConnection().ConnectionString;
using (SqlBulkCopy bulk = new SqlBulkCopy(connectionString))
{
try
{
bulk.DestinationTableName = "Author";
bulk.BatchSize = table.Rows.Count;
bulk.WriteToServer(table);
return 1;
}
catch
{
return 0;
}
}
}
}
}
使用SqlBulkCopy
执行批量导入的效率非常高,运行结果如下所示:
7、Z.EntityFramework.Extensions.EFCore添加实体
如果你在.NET Framework
时代就开始接触Entity Framework
,那一定听说过Z.EntityFramework
的大名,这个扩展类库对于批量操作的支持相当友好。使用NuGet
引入该组件,由于项目平台为.NET Core 3.1
,因此Z.EntityFramework.Extensions.EFCore
的版本选择3.18.0
即可。
Z.EntityFramework.Extensions.EFCore
批量导入的代码如下所示:
using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
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> Add()
{
// 模拟一个Author集合
List<Author> authors = new List<Author>();
Random random = new Random();
for (int i = 1; i < 10000; i++)
{
authors.Add(new Author
{
Name = "Author_" + i.ToString(),
Gender = i % 2 == 0 ? "男" : "女",
Age = random.Next(20, 50),
Email = "00000000@qq.com"
});
}
// 批量导入
try
{
_dbContext.BulkInsert(authors);
return 1;
}
catch
{
return 0;
}
}
}
}
使用Z.EntityFramework.Extensions.EFCore
批量导入的速度也非常快,运行结果如下所示:
8、结语
本文主要介绍了EFCore
中添加实体的相关操作。在实际开发过程中,如果需要添加的实体集合的数据量较大,可以考虑使用SqlBulkCopy
或Z.EntityFramework.Extensions.EFCore
来实现实体的批量添加。