ASP.NET Core 3.1系列(19)——EFCore中的添加实体操作

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 Email
1 张三 35 [email protected]
2 李四 40 [email protected]
3 王五 37 [email protected]

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

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 = "[email protected]"
            };
            _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 = "[email protected]"
            };
            _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'[email protected]',@p2=N'女',@p3=N'赵六'

4、批量添加实体

既然AddEntry方法可以实现添加单个实体的功能,那我们自然会想到在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 = "[email protected]"
                },
                new Author
                {
                    Name = "周二",
                    Gender = "女",
                    Age = 26,
                    Email = "[email protected]"
                },
                new Author
                {
                    Name = "周三",
                    Gender = "男",
                    Age = 27,
                    Email = "[email protected]"
                }
            };
            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 = "[email protected]"
                },
                new Author
                {
                    Name = "周二",
                    Gender = "女",
                    Age = 26,
                    Email = "[email protected]"
                },
                new Author
                {
                    Name = "周三",
                    Gender = "男",
                    Age = 27,
                    Email = "[email protected]"
                }
            };
            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 = "[email protected]"
                },
                new Author
                {
                    Name = "周二",
                    Gender = "女",
                    Age = 26,
                    Email = "[email protected]"
                },
                new Author
                {
                    Name = "周三",
                    Gender = "男",
                    Age = 27,
                    Email = "[email protected]"
                }
            };
            _dbContext.Set<Author>().AddRange(authors);
            return _dbContext.SaveChanges();
        }
    }
}

这三种方法都可以实现批量添加实体的功能,它们都会在后台生成3SQL

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'[email protected]',@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'[email protected]',@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'[email protected]',@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 = "[email protected]"
                    });

                    _dbContext.Set<Author>().Add(new Author
                    {
                        Name = "周二",
                        Gender = "女",
                        Age = 26,
                        Email = "[email protected]"
                    });

                    _dbContext.Set<Author>().Add(new Author
                    {
                        Name = "周三",
                        Gender = "男",
                        Age = 27,
                        Email = "[email protected]"
                    });

                    _dbContext.SaveChanges();
                    _dbContext.Database.CommitTransaction();
                    return 1;
                }
                catch
                {
                    _dbContext.Database.RollbackTransaction();
                    return 0;
                }
            }
        }
    }
}

与上面批量添加实体的结果一样,后台会生成3SQL

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'[email protected]',@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'[email protected]',@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'[email protected]',@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 = "[email protected]"
                });
            }

            // 转换为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执行批量导入的效率非常高,运行结果如下所示:

ASP.NET Core 3.1系列(19)——EFCore中的添加实体操作_第1张图片

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

ASP.NET Core 3.1系列(19)——EFCore中的添加实体操作_第2张图片
批量导入的代码如下所示:

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 = "[email protected]"
                });
            }

            // 批量导入
            try
            {
                _dbContext.BulkInsert(authors);
                return 1;
            }
            catch
            {
                return 0;
            }
        }
    }
}

使用Z.EntityFramework.Extensions.EFCore批量导入的速度也非常快,运行结果如下所示:

ASP.NET Core 3.1系列(19)——EFCore中的添加实体操作_第3张图片

8、结语

本文主要介绍了EFCore中添加实体的相关操作。在实际开发过程中,如果需要添加的实体集合的数据量较大,可以考虑使用SqlBulkCopyZ.EntityFramework.Extensions.EFCore来实现实体的批量添加。

你可能感兴趣的:(ASP.NET,Core,C#,ASP.NET,Core)