删除EF Core6中的所有行

目录

背景

清洁助手

注释助手

扩展方法

使用代码

Delete

Truncate

Clear

单元测试

在db上下文中添加内容

测试方法

程序集伪造

Moq.EntityFrameworkCore

局限性

引用

关于代码示例


背景

有时,我们可能需要使用实体框架核心从表中删除所有记录。常用的方法之一是迭代每一行并使用DBSet<T>.Remove()删除每一行,如下所示:

foreach (var item in Db.Users)
{
    Db.Users.Remove(item);
}
Db.SaveChanges();

此过程比Truncate/Delete慢,不适合大型数据集。在这里,将检查替代选项,例如运行Truncate/Delete命令和单元测试选项。

清洁助手

其思想是使用原始SQL查询来Truncate表或Delete表中的所有内容。

注释助手

注释帮助程序是从模型的实体框架映射配置中获取表名和架构。

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;

namespace EfCoreHelper.Database.Core
{
    public class AnnotationHelper
    {
        private static string GetName(IEntityType entityType, 
                                      string defaultSchemaName = "dbo")
        {
            /*3.0.1 these were working*/
            //var schemaName = entityType.GetSchema();
            //var tableName = entityType.GetTableName();
            /*5 and 6 these are working*/
            var schema = entityType.FindAnnotation("Relational:Schema").Value;
            string tableName = entityType.GetAnnotation
                               ("Relational:TableName").Value.ToString();
            string schemaName = schema == null ? defaultSchemaName : schema.ToString();
            /*table full name*/
            string name = string.Format("[{0}].[{1}]", schemaName, tableName);
            return name;
        }

        public static string TableName<T>(DbContext dbContext) where T : class
        {
            var entityType = dbContext.Model.FindEntityType(typeof(T));
            return GetName(entityType);
        }

        public static string TableName<T>(DbSet<T> dbSet) where T : class
        {
            var entityType = dbSet.EntityType;
            return GetName(entityType);
        }
    }
}

扩展方法

Microsoft.EntityFrameworkCore一起,我们需要安装Microsoft.EntityFrameworkCore.Relational。这将使我们能够使用ExecuteSqlRaw()在实体框架核心中运行行查询。

Install-Package Microsoft.EntityFrameworkCore
Install Microsoft.EntityFrameworkCore.Relational

以下是DbSet<T>DbContext的扩展方法:

  • Truncate——使用truncate查询截断表
  • Delete——使用delete查询删除表的所有行
  • Clear——使用RemoveRange方法删除表的所有行

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using System.Linq;

namespace EfCoreHelper.Database.Core
{
    public static class EfCleanHelper
    {
        public static string Truncate<T>(this DbSet<T> dbSet) where T : class
        {
            string cmd = $"TRUNCATE TABLE {AnnotationHelper.TableName(dbSet)}";
            var context = dbSet.GetService<ICurrentDbContext>().Context;
            context.Database.ExecuteSqlRaw(cmd);
            return cmd;
        }

        public static string Delete<T>(this DbSet<T> dbSet) where T : class
        {
            string cmd = $"DELETE FROM {AnnotationHelper.TableName(dbSet)}";
            var context = dbSet.GetService<ICurrentDbContext>().Context;
            context.Database.ExecuteSqlRaw(cmd);
            return cmd;
        }

        public static void Clear<T>(this DbContext context) where T : class
        {
            DbSet<T> dbSet = context.Set<T>();
            if (dbSet.Any())
            {
                dbSet.RemoveRange(dbSet.ToList());
            }
        }

        public static void Clear<T>(this DbSet<T> dbSet) where T : class
        {
            if (dbSet.Any())
            {
                dbSet.RemoveRange(dbSet.ToList());
            }
        }

        public static string Truncate(this DbContext context, 
                      string tableName, string schemaName = "dbo")
        {
            string name = string.Format("[{0}].[{1}]", schemaName, tableName);
            string cmd = $"TRUNCATE TABLE { name }";
            context.Database.ExecuteSqlRaw(cmd);
            return cmd;
        }

        public static string Delete(this DbContext context, 
                      string tableName, string schemaName = "dbo")
        {
            string name = string.Format("[{0}].[{1}]", schemaName, tableName);
            string cmd = $"DELETE FROM { name }";
            context.Database.ExecuteSqlRaw(cmd);
            return cmd;
        }
    }
}

使用代码

Delete

使用名称

Db.Delete("Users");

使用DbSet<T>

Db.Users.Delete();

使用事务

using (var tran = Db.Database.BeginTransaction())
{
    try
    {
        Db.Users.Delete();
        //or
        //Db.Delete("Users");

        tran.Commit();
    }
    catch (Exception ex)
    {
        tran.Rollback();
    }
}

Truncate

使用名称

Db.Truncate("Users");

使用DbSet<T>

Db.Users.Truncate();

Clear

使用DbSet<T>

Db.Users.Clear();
Db.SaveChanges();

使用DbContext

Db.Clear<User>();
Db.SaveChanges();

单元测试

让我们将包添加到测试项目中:

  • 用于实体框架DbContext模拟的最小起订量
  • 用于填充样本测试数据的NBuilder

Install-Package Nunit
Install-Package Moq
Install-Package NBuilder

Clear方法适用于单元测试。但是TruncateDelete方法运行原始SQL,因此对于单元测试,我们需要绕过它们。

db上下文中添加内容

在这里,CpuAppDbContext db上下文类正在实现ICpuAppDbContext接口。其强制添加void Truncate<T>() where T : classvoid Delete<T>() where T : class方法,这将用于truncate表或删除表的数据。在实际实现中,我们调用了现有的扩展方法。

public interface ICpuAppDbContext : IDisposable
{
    DbSet<User> Users { get; set; }
    void Truncate<T>() where T : class;
    void Delete<T>() where T : class;

    DbSet<T> Set<T>() where T : class;
    int SaveChanges();
}

public class CpuAppDbContext : DbContext, ICpuAppDbContext
{
    public CpuAppDbContext(DbContextOptions<CpuAppDbContext> options) : base(options)
    {
    }
    public CpuAppDbContext() : base()
    {
    }

    public DbSet<User> Users { get; set; }

    public void Truncate<T>() where T : class
    {
        this.Set<T>().Truncate();
    }

    public void Delete<T>() where T : class
    {
        this.Set<T>().Delete();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new UserConfig());
    }
}

在实际代码中使用方法

Db.Delete<User>();
Db.Truncate<User>();

测试方法

List<T>转换为DbSet<T>的帮助程序方法:

public static class TestHelpers
{
    public static DbSet<T> ToDbSet<T>(this List<T> list) where T : class
    {
        IQueryable<T> queryableList = list.AsQueryable();
        var mockSet = new Mock<DbSet<T>>();
        mockSet.As<IQueryable<T>>().Setup(m => 
                             m.Provider).Returns(queryableList.Provider);
        mockSet.As<IQueryable<T>>().Setup(m => 
                             m.Expression).Returns(queryableList.Expression);
        mockSet.As<IQueryable<T>>().Setup(m => 
                             m.ElementType).Returns(queryableList.ElementType);
        mockSet.As<IQueryable<T>>().Setup(m => 
                             m.GetEnumerator()).Returns(queryableList.GetEnumerator());
        return mockSet.Object;
    }
}

使用接口进行单元测试

在这里,我们绕过/模拟了该Delete方法:

[Test]
public void DbContext_Moq_Using_Interface()
{
    List<User> users = Builder<User>.CreateListOfSize(10).Build().ToList();
    bool isCalled = false;

    var dbMock = new Mock<ICpuAppDbContext>();
    dbMock.Setup(x => x.Users).Returns(users.ToDbSet());
    dbMock.Setup(x => x.Delete<User>()).Callback(() =>
    {
        users = new List<User>();
        isCalled = true;
    });

    /*will pass this db to repo*/
    ICpuAppDbContext db = dbMock.Object;
    db.Delete<User>();
    Assert.True(isCalled);
    //Assert.False(db.Users.Any());
}

使用DbContext类进行单元测试

对于模拟,我们必须将可测试的数据库集和方法声明为虚拟的。这个虚拟的东西也与懒惰和渴望加载有关。因此,在使用或更改现有代码之前,请确保。我确实更喜欢基于界面的模拟,这是最好的选择。

[Test]
public void DbContext_Moq_Using_Class()
{
    List<User> users = Builder<User>.CreateListOfSize(10).Build().ToList();
    bool isCalled = false;

    var dbMock = new Mock<CpuAppDbContext>();
    dbMock.Setup(x => x.Users).Returns(users.ToDbSet());        /*in dbcontext,
          need to add virtual, public virtual DbSet<User> Users { get; set; }*/
    dbMock.Setup(x => x.Delete<User>()).Callback(() =>          /*in dbcontext,
          need to add virtual, public virtual void Delete<T>() where T : class*/
    {
        users = new List<User>();
        isCalled = true;
    });

    /*will pass this db to repo*/
    CpuAppDbContext db = dbMock.Object;
    db.Delete<User>();
    Assert.True(isCalled);
    //Assert.False(db.Users.Any());
}

程序集伪造

为了模拟扩展方法,也可以使用伪造程序集

Moq.EntityFrameworkCore

如果我们想使用上下文类本身进行单元测试,我们也可以使用Moq.EntityFrameworkCore。但它需要EF Core 6

Install-Package Moq.EntityFrameworkCore

局限性

  • TruncateDelete SQL语句立即执行,无论我们是否调用Db.SaveChanges()
  • ClearTruncate/Delete慢,不适合大型数据集。
  • Clear适用于常规单元测试,而TruncateDelete不适合。

引用

关于代码示例

  • Visual Studio 2022 Solution
  • ASP.NET 6
  • EF Core 6,也在Core 5中进行了测试

Database.Test是一个有趣的单元测试项目。在 appsettings.json 中更改连接字符串。在数据库中创建用户表,检查项目Database.Applicationdb.sql。检查/运行EfCleanHelperTests.csEfCleanHelperUnitTests.cs 的测试。

{
  "ConnectionStrings": {
    "DatabaseConnection": "Data Source=.\\SQLEXPRESS;
     Initial Catalog=Cup;Integrated Security=True"
  }
}

DROP TABLE IF EXISTS [dbo].[Users]
GO
CREATE TABLE [dbo].[Users](
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](max) NULL,
    [Email] [nvarchar](max) NULL,
    [IsDeleted] [bit] NOT NULL,
    [CreatedOn] [datetime2](7) NOT NULL,
    [CreatedBy] [nvarchar](max) NOT NULL,
    [ModifiedOn] [datetime2](7) NULL,
    [ModifiedBy] [nvarchar](max) NULL
)

https://www.codeproject.com/Articles/5339402/Delete-All-Rows-in-Entity-Framework-Core-6

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值