实体框架.删除表中的所有行

本文关键字:框架 删除 实体 | 更新日期: 2023-09-27 18:02:22

如何使用实体框架快速删除表中的所有行?

我正在使用:

var rows = from o in dataDb.Table
           select o;
foreach (var row in rows)
{
    dataDb.Table.Remove(row);
}
dataDb.SaveChanges();

但是,它需要很长时间来执行。

有其他选择吗?

实体框架.删除表中的所有行

对于那些像我一样在谷歌上搜索这个并最终在这里结束的人,这是你目前在EF5和EF6中的做法:

context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");

假设上下文是System.Data.Entity.DbContext

编辑:

当前在net6.0 (dotnet 6 core)中,您可以做以下操作:

context.Database.ExecuteSqlRaw("TRUNCATE TABLE [TableName]");

或者使用Async重载:

await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE [TableName]");

这些扩展方法来自Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions

如果你有外键的问题(在MySql中),你可能要做以下(做SET FOREIGN_KEY_CHECKS = 0;部分在一个单独的调用似乎不适合我)

context.Database.ExecuteSqlRaw("SET FOREIGN_KEY_CHECKS = 0; TRUNCATE TABLE [TableName];");

所以如果你想截断你的整个数据库(可能是出于单元测试的原因)-你可以这样做:

var tableNames = context.Model.GetEntityTypes()
    .Select(t => t.GetTableName())
    .Distinct()
    .ToList();
foreach (var tableName in tableNames)
{
    context.Database.ExecuteSqlRaw($"SET FOREIGN_KEY_CHECKS = 0; TRUNCATE TABLE `{tableName}`;");
}

警告: 以下内容仅适用于小表(考虑<1000行)

这是一个使用实体框架(不是SQL)来删除行的解决方案,所以它不是SQL引擎(R/DBM)特定的。

这假设您正在做测试或一些类似的情况。

  • 数据量小
  • 性能不重要

简单地调用:

VotingContext.Votes.RemoveRange(VotingContext.Votes);

假设上下文:

public class VotingContext : DbContext
{
    public DbSet<Vote> Votes{get;set;}
    public DbSet<Poll> Polls{get;set;}
    public DbSet<Voter> Voters{get;set;}
    public DbSet<Candidacy> Candidates{get;set;}
}

对于更整洁的代码,您可以声明以下扩展方法:

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

则以上变为:

VotingContext.Votes.Clear();
VotingContext.Voters.Clear();
VotingContext.Candidacy.Clear();
VotingContext.Polls.Clear();
await VotingTestContext.SaveChangesAsync();

我最近使用这种方法为每个测试用例清理我的测试数据库(它显然比每次从头重新创建数据库要快,尽管我没有检查生成的删除命令的形式)。


为什么会慢?

  1. EF将获得所有的行(VotingContext.Votes)
  2. ,然后使用他们的id(不确定具体如何,没关系),删除他们。

所以如果你正在处理大量的数据,你将杀死SQL server进程(它将消耗所有的内存)和IIS进程,因为EF将缓存所有的数据与SQL server相同的方式。如果您的表包含大量数据,则不要使用此方法。

更新EF Core 7+,我们现在有一个可用的批量删除方法作为EF Core的一部分,这意味着特定的方法是由底层提供者(即。

原始代码以及其他一些答案的问题是,您正在将表中的所有实体加载到内存中,以便能够删除它们。EF Core 7附带了一个解决这个问题的方法。

你现在可以这样做:

await context.Table.ExecuteDeleteAsync();

对于SQL实现,这将映射到正在生成的DELETE FROM查询。据我所知,NoSQL (Cosmos DB)提供程序中不支持此功能。

请注意,您仍然必须考虑关系删除(您可以简单地在任何相关表上使用相同的调用),以及TRUNCATE TABLE的丢失和不再重置任何Identity属性。但是你可以自己重置(SQL Server):

DBCC CHECKIDENT ('[Table]', RESEED, 0);

原始:

使用SQL的TRUNCATE TABLE命令将是最快的,因为它在表上操作,而不是在单个行上操作。

dataDb.ExecuteStoreCommand("TRUNCATE TABLE [Table]");

假设dataDbDbContext(而不是ObjectContext),您可以包装它并使用如下方法:

var objCtx = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)dataDb).ObjectContext;
objCtx.ExecuteStoreCommand("TRUNCATE TABLE [Table]");
var all = from c in dataDb.Table select c;
dataDb.Table.RemoveRange(all);
dataDb.SaveChanges();
using (var context = new DataDb())
{
     var ctx = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)context).ObjectContext;
     ctx.ExecuteStoreCommand("DELETE FROM [TableName] WHERE Name= {0}", Name);
}

using (var context = new DataDb())
{
     context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
}

您可以不使用Foreach

dataDB.Table.RemoveRange(dataDB.Table);
dataDB.SaveChanges();

这将删除所有的行

这避免使用任何sql

using (var context = new MyDbContext())
{
    var itemsToDelete = context.Set<MyTable>();
    context.MyTables.RemoveRange(itemsToDelete);
    context.SaveChanges();
}

EF Core 7.0通过添加批量更新和删除语义一劳永逸地解决了这个问题:

await dataDB.Table.ExecuteDeleteAsync();

注意,这个语法立即执行底层(SQL)命令从表中删除数据。它不需要跟踪实体,标记它要删除,并等待UpdateDatabase对数据库执行事务。

还要注意,默认情况下,多个ExecuteDeleteExecuteUpdate命令不会包含在单个事务中。但是,DbContext事务api可以以正常的方式使用,将这些命令包装在事务中。

context.TableName.RemoveRange(context.TableName);
context.SaveChanges();

当我必须处理一个特殊情况时,我遇到了这个问题:完全更新"叶子"表中的内容(没有fk指向它)。这涉及到删除所有行并放入新行信息,并且应该以事务方式完成(如果insert由于任何原因失败,我不希望最终得到一个空表)。

我已经尝试了public static void Clear<T>(this DbSet<T> dbSet)方法,但没有插入新行。另一个缺点是整个过程很慢,因为一行一行地删除。

所以,我已经切换到TRUNCATE方法,因为它更快,它也是可ROLLBACKable。它还会重置标识。

使用存储库模式的示例:
public class Repository<T> : IRepository<T> where T : class, new()
{
    private readonly IEfDbContext _context;
    public void BulkInsert(IEnumerable<T> entities)
    {
        _context.BulkInsert(entities);
    }
    public void Truncate()
    {
        _context.Database.ExecuteSqlCommand($"TRUNCATE TABLE {typeof(T).Name}");
    }
 }
 // usage 
 DataAccess.TheRepository.Truncate();
 var toAddBulk = new List<EnvironmentXImportingSystem>();
 // fill toAddBulk from source system
 // ...
 DataAccess.TheRepository.BulkInsert(toAddBulk);
 DataAccess.SaveChanges();

当然,正如已经提到的,这个解决方案不能被外键引用的表使用(TRUNCATE失败)。

If

using(var db = new MyDbContext())
{
    await db.Database.ExecuteSqlCommandAsync(@"TRUNCATE TABLE MyTable"););
}

原因

不能截断表'MyTable',因为它被一个FOREIGN KEY约束引用。

我用这个:

using(var db = new MyDbContext())
{
    await db.Database.ExecuteSqlCommandAsync(@"DELETE FROM MyTable WHERE ID != -1");
}
var data = (from n in db.users select n);
db.users.RemoveRange(data);
db.SaveChanges();

以下工作在SQLite数据库上(使用实体框架)。

似乎清除所有db表的最快方法是使用context.Database.ExecuteSqlCommand("some SQL"),正如上面一些注释所强调的那样。这里我也将展示如何重置表的'index'计数。

context.Database.ExecuteSqlCommand("delete from TableA");
context.Database.ExecuteSqlCommand("delete from sqlite_sequence where name='TableA'");//resets the autoindex
context.Database.ExecuteSqlCommand("delete from TableB");
context.Database.ExecuteSqlCommand("delete from sqlite_sequence where name='TableB'");//resets the autoindex 
context.Database.ExecuteSqlCommand("delete from TableC");
context.Database.ExecuteSqlCommand("delete from sqlite_sequence where name='TableC'");//resets the autoindex 

重要的一点是,如果在表中使用外键,必须首先在父表之前删除子表,因此在删除时表的顺序(层次结构)很重要,否则可能会发生SQLite异常。

注意:var context = new YourContext()

如果您希望清除整个数据库

由于外键约束,截断表的顺序很重要。这是一种强制执行这个序列的方法。

    public static void ClearDatabase<T>() where T : DbContext, new()
    {
        using (var context = new T())
        {
            var tableNames = context.Database.SqlQuery<string>("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME NOT LIKE '%Migration%'").ToList();
            foreach (var tableName in tableNames)
            {
                foreach (var t in tableNames)
                {
                    try
                    {
                        if (context.Database.ExecuteSqlCommand(string.Format("TRUNCATE TABLE [{0}]", tableName)) == 1)
                            break;
                    }
                    catch (Exception ex)
                    {
                    }
                }
            }
            context.SaveChanges();
        }
    }

用法:

ClearDatabase<ApplicationDbContext>();

记住在此之后重新实例化DbContext。

在EFCore(我使用的版本是3.1)中,您可以使用以下命令删除所有行-

context.Database.ExecuteSqlRaw("TRUNCATE TABLE [TableName]");

这适用于我…EF v3.1.5

context.ModelName.RemoveRange(context.ModelName.ToList());
context.SaveChanges();

这在EF 5中正常工作:

YourEntityModel myEntities = new YourEntityModel();
var objCtx = ((IObjectContextAdapter)myEntities).ObjectContext;
objCtx.ExecuteStoreCommand("TRUNCATE TABLE [TableName]");

删除所有记录。不要像"truncate"那样重置主索引。

/// <summary>
/// SET - DELETE all record by table - no truncate - return deleted records
/// </summary>
public static int setListDelAllMYTABLE()
{
    // INIT
    int retObj = 0;
    using (MYDBEntities ctx = new MYDBEntities())
    {
        // GET - all record
        var tempAllRecord = ctx.MYTABLE.ToList();
        // RESET
        ctx.MYTABLE.RemoveRange(tempAllRecord);
        // SET - final save
        retObj += ctx.SaveChanges();
    }
    // RET
    return retObj;
}

如果是MVC,可以这样做:

public async Task<IActionResult> DeleteAll()
{
    var list = await _context.YourClass.ToListAsync();
    _context.YourClass.RemoveRange(list);
    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}

确保当您试图删除父级时,所有子级将在删除时级联。或者子节点有可空的外键。

下面是Ron的流行解决方案的一个变体,它通过利用堆栈溢出的另一个流行解决方案来确定实体框架类的底层表名,从而避免使用硬编码的字符串表名。

使用这些扩展方法,解决方案如下:
_dbContext.TruncateTable<TheTableName>();

(如果您在EF DBContext类或部分类文件中编辑代码,请使用this.TruncateTable<...)

这是扩展类:

public static class EntityFrameworkExtensions
{
    private static string ParseTableNameFromSQL(string sql)
    {
        Regex regex = new Regex("FROM (?<table>.*) AS");
        Match match = regex.Match(sql);
        string table = match.Groups["table"].Value;
        return table;
    }
    
    public static string GetTableName<T>(this IObjectContextAdapter context) where T : class =>
        ParseTableNameFromSQL(context.ObjectContext.CreateObjectSet<T>().ToTraceString());
    
    public static void TruncateTable<T>(this DbContext dbContext) where T : class =>
        dbContext.Database.ExecuteSqlCommand($"TRUNCATE TABLE {dbContext.GetTableName<T>()}");
    public static void DeleteAllTableRows<T>(this DbContext dbContext) where T : class =>
        dbContext.Database.ExecuteSqlCommand($"DELETE FROM {dbContext.GetTableName<T>()}");
}

最后一个扩展方法DeleteAllTableRows是一个有用的替代,如果你的表不能被截断(例如,由于外键引用)-这仍然比实体框架RemoveAll替代快得多。

适用于EF Core 3

public static class EntityExtensions
{
    public static async Task ClearAsync<T>(this DbSet<T> dbSet) where T : class
    {
        var command = dbSet.CreateDbCommand();
        command.CommandText = $"TRUNCATE TABLE {dbSet.EntityType.GetSchema()}.{dbSet.EntityType.GetTableName()}";
        await command.ExecuteNonQueryAsync();
    }
}

,但请注意dbSet。CreateDbCommand是一个扩展

我的解决方案,混合我的想法,一些答案(罗恩一个从这个线程,但也是这个和这个反思),并试图涵盖一些不同的条件。

它是基于EF6,但它应该工作得很好,只是修复一些扩展,如GetTableName<TEntity>

我的解决方案:

  • 使用扩展,所以你只需要DbSet来启动
  • 有一个行数阈值,以决定RemoveRange或SQL执行,以避免性能问题
  • SQL版本是基于DELETE而不是TRUNCATE,以避免外键问题(它必须符合您的要求,当然)
  • 有一个参数来保存内联
private const int RangeLimit = 100;
private static void ClearTable<TEntity>(this DbSet<TEntity> dataSet, bool saveChanges = true) where TEntity : class
{
    DbContext context = null;
    if (dataSet.Count() > RangeLimit)
    {
        context = dataSet.GetContext();
        context.Database.ExecuteSqlCommand($"DELETE FROM [{context.GetTableName<TEntity>()}]");
    }
    else
    {
        dataSet.RemoveRange(dataSet);
    }
    if (!saveChanges)
    {
        return;
    }
    if (context == null)
    {
        context = dataSet.GetContext();
    }
    context.SaveChanges();
}
private static DbContext GetContext<TEntity>(this DbSet<TEntity> dbSet)
    where TEntity : class
{
    var internalSet = dbSet
        .GetType()
        .GetField("_internalSet", BindingFlags.NonPublic | BindingFlags.Instance)
        ?.GetValue(dbSet);
    var internalContext = internalSet?.GetType().BaseType
        ?.GetField("_internalContext", BindingFlags.NonPublic | BindingFlags.Instance)
        ?.GetValue(internalSet);
    return (DbContext)internalContext?.GetType()
        .GetProperty("Owner", BindingFlags.Instance | BindingFlags.Public)
        ?.GetValue(internalContext, null);
}
public static string GetTableName<TEntity>(this DbContext context) where TEntity : class
{
    return (context as IObjectContextAdapter).ObjectContext.CreateObjectSet<TEntity>().EntitySet.Name;
}
对于一个名为Entries的数据库表,您所要做的就是:
databaseContext.Entries.ClearTable();

如果您想保存更改,或者如果您不想保存更改:

databaseContext.Entries.ClearTable(false);

它是基于反射,以简化代码。当然,它有一些性能上的折衷,但是每个表只发生一次反射,因此在这些条件下应该是完全可以接受的。

这是一个非常干净的解决方案。

_context.RemoveRange(_context.ModelName);
_context.SaveChanges();

这里有几个问题,几乎所有的答案都在这里:

1]硬编码sql。括号是否适用于所有数据库引擎?
2]实体框架RemoveRemoveRange调用。这会将受操作影响的所有实体加载到内存中。呵。
截断表。打破外键引用,可能不能在所有数据库引擎中工作。

使用https://entityframework-plus.net/,他们处理跨数据库平台的东西,将删除转换成正确的sql语句,不将实体加载到内存中,并且库是免费和开源的。

免责声明:我不隶属于nuget包。