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

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

我目前使用:

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

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

还有其他选择吗?

339539 次浏览

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

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

假设dataDb是一个DbContext(不是ObjectContext),你可以将它包装起来并像这样使用方法:

var objCtx = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)dataDb).ObjectContext;
objCtx.ExecuteStoreCommand("TRUNCATE TABLE [Table]");
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]");
}

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

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

假设context是System.Data.Entity.DbContext

编辑:

目前在net6.0 (dotnet 6核心)你可以做以下事情:

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}`;");
}
var all = from c in dataDb.Table select c;
dataDb.Table.RemoveRange(all);
dataDb.SaveChanges();

如果

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");
}

< em >警告:以下仅适用于小表(think <1000行)< / em >

这里是一个使用实体框架(而不是SQL)删除行的解决方案,因此它不是SQL Engine(R/DBM)特定的。

这假设你这样做是为了测试或类似的情况。 < / p >

  • 数据量小
  • 表现不重要

简单地调用:

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相同的方式。如果您的表包含大量数据,请不要使用此方法。

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

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

    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。

这在EF 5中正常工作:

YourEntityModel myEntities = new YourEntityModel();


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

这避免了使用任何sql

using (var context = new MyDbContext())
{
var itemsToDelete = context.Set<MyTable>();
context.MyTables.RemoveRange(itemsToDelete);
context.SaveChanges();
}
var data = (from n in db.users select n);
db.users.RemoveRange(data);
db.SaveChanges();

我在处理一个特殊情况时遇到了这个问题:完全更新“叶”表中的内容(没有指向它的fk)。这涉及到删除所有行并放入新行信息,这应该以事务方式完成(如果由于某种原因插入失败,我不想以空表结束)。

我已经尝试了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失败)。

没有Foreach你也能做到

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

这将删除所有行

删除所有记录。不要像“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;
}

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

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

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()

如果是MVC,你可以这样做:

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

在EFCore(我使用的版本是3.1),你可以使用以下方法删除所有行-

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

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

这对我很有用……EF v3.1.5

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

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

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

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

免责声明:我不隶属于nuget包。他们确实提供了付费版本,功能甚至更多。

下面是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核心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是一个扩展

Ef Core 3.1及以上

 _context.Database.ExecuteSqlRaw($"TRUNCATE TABLE
{_context.Warehouse.EntityType.GetSchema()}.
{_context.Warehouse.EntityType.GetTableName()}");

仓库是dbSet

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

它基于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);

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