如何使用实体框架快速删除表中的所有行?
我目前正在使用:
var rows = from o in dataDb.Table
select o;
foreach (var row in rows)
{
dataDb.Table.Remove(row);
}
dataDb.SaveChanges();
但是,执行需要很长时间。
有没有其他选择?
TRUNCATE
专家都不担心外键约束。
[TableName]
),则它不可移植。
对于那些像我一样在谷歌上搜索并最终来到这里的人,这就是您目前在 EF5 和 EF6 中执行此操作的方式:
context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
假设上下文是 System.Data.Entity.DbContext
警告:以下仅适用于小表(认为 < 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();
我最近使用这种方法为每个测试用例运行清理我的测试数据库(这显然比每次从头开始重新创建数据库要快,尽管我没有检查生成的删除命令的形式)。
为什么会慢?
EF 将获取所有行(VotingContext.Votes),然后将使用它们的 ID(不确定如何,没关系)来删除它们。
因此,如果您正在处理大量数据,您将终止 SQL Server 进程(它将消耗所有内存),而 IIS 进程也是如此,因为 EF 将以与 SQL Server 相同的方式缓存所有数据。如果您的表包含大量数据,请不要使用这个。
RemoveRange
之后明确地调用 _context.SaveChanges()
使用 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]");
TRUNCATE TABLE
更改为 DELETE FROM
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]");
}
IQueryable
上没有扩展方法 Delete
——我猜 Manish 正在使用类似 EntityFramework.Extended: github.com/loresoft/EntityFramework.Extended
.Delete
是一个自定义扩展,在发布答案的时候,完全忘了提及这个自定义 .Delete
的定义。 :)
你可以在没有 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();
}
当我不得不处理一个特殊情况时,我遇到了这个问题:完全更新“叶”表中的内容(没有 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 失败)。
Cannot truncate table because it is being referenced by a FOREIGN KEY constraint
),即使它们是空的,所以 FK 必须是 dropped and recreated 才能使用 TRUNCATE
。
context.TableName.RemoveRange(context.TableName);
context.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");
}
var data = (from n in db.users select n);
db.users.RemoveRange(data);
db.SaveChanges();
如果您想清除整个数据库。
由于外键约束,截断表的顺序很重要。这是一种暴力破解此序列的方法。
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。
以下适用于 SQLite 数据库(使用实体框架)。
似乎清除所有数据库表的最快方法是使用 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()
在 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]");
适用于 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 是一个扩展
删除所有记录。不要像“截断”那样重置主索引。
/// <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;
}
在我的代码中,我并没有很好地访问 Database 对象,因此您可以在 DbSet 上执行此操作,您也可以在其中使用任何类型的 sql。它会像这样结束:
var p = await _db.Persons.FromSql("truncate table Persons;select top 0 * from Persons").ToListAsync();
如果是 MVC,你可以这样做:
public async Task<IActionResult> DeleteAll()
{
var list = await _context.YourClass.ToListAsync();
_context.YourClass.RemoveRange(list);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
确保当您尝试删除父级时,所有子级将在删除时级联。或者孩子有可以为空的外键。
这是 Ron 对流行的 solution 的一个变体,它通过利用另一个流行的堆栈溢出解决方案来确定实体框架类的基础表名,从而避免使用硬编码字符串表名。
使用这些扩展方法,解决方案如下所示:
_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
替代方法快得多。
DELETE FROM
扩展名(实际上,在许多情况下我经常不得不依靠它)
System.Data.Entity
的 DbContext
而不是 Microsoft.EntityFrameworkCore
。您说这是基于“堆栈溢出的流行解决方案”;你有这个流行解决方案的链接吗?
var list = db.Discounts.ToList().Select(x => x as Discount);
foreach (var item in list)
{
db.Discounts.Remove(item);
}
db.SaveChanges();
这里几乎所有的答案都有几个问题:
1]硬编码的sql。括号是否适用于所有数据库引擎?
2] 实体框架 Remove
和 RemoveRange
调用。这会将所有实体加载到受操作影响的内存中。哎呀。
3] 截断表。与外键引用中断,可能不适用于所有数据库引擎。
使用 https://entityframework-plus.net/,他们处理跨数据库平台的东西,将删除转换为正确的 sql 语句并且不将实体加载到内存中,并且该库是免费和开源的。
免责声明:我不隶属于 nuget 包。他们确实提供了一个付费版本,可以做更多的事情。
[
转义是特定于 SQL Server 的,但TRUNCATE
命令不是 - 它是 ANSI SQL 的一部分,因此适用于大多数 SQL 方言(尽管不是 SQLite)。