简体   繁体   English

实体框架中的批量删除

[英]bulk delete in entity framework

I'd like to bulk delete records from a table using linq. 我想使用linq批量删除表中的记录。 There's a post that describes how to do it: Bulk-deleting in LINQ to Entities 有一篇帖子描述了如何做到这一点: LINQ to Entities中的批量删除

var query = from c in ctx.Customers
            where c.SalesPerson.Email == "..."
            select c;

query.Delete();

But the function "Delete" doesn't exist in my var variable. 但是我的var变量中不存在“删除”功能。
Furthermore, the function "SubmitChanges" doesn't exist on my context. 此外,我的上下文中不存在“SubmitChanges”功能。

有一个有趣的NuGet包, 可以让你进行批量删除和更新

There is no currently supported bulk delete baked into Entity Framework. 实体框架中目前不支持批量删除。 Its actually one of the features being discussed on codeplex now EF is open-source. 它实际上是在codeplex上讨论的功能之一,现在EF是开源的。

EntityFramework.Extended provides batch delete support (you can find this in nuget) however my experience is that it has some performance issues. EntityFramework.Extended提供批量删除支持(你可以在nuget中找到它)但是我的经验是它有一些性能问题。

This code adds a simple extension method to any DbContext that will bulk delete all data in any table referenced within the entity framework query you provide. 此代码为任何DbContext添加了一个简单的扩展方法,该方法将批量删除您提供的实体框架查询中引用的任何表中的所有数据。 It works by simply extracting all table names involved in the query, and attempting to delete the data by issuing a "DELETE FROM tablename" SQL query, which is common across most types of database. 它的工作原理是简单地提取查询中涉及的所有表名,并尝试通过发出“DELETE FROM tablename”SQL查询来删除数据,这在大多数类型的数据库中都很常见。

To use, simply do this: 要使用,只需执行以下操作:

myContext.BulkDelete(x => x.Things);

which will delete everything in the table linked to the Things entity store. 这将删除链接到Things实体商店的表中的所有内容。

The code: 编码:

using System.Linq;
using System.Text.RegularExpressions;

namespace System.Data.Entity {

    public static class DbContextExtensions {

        /// <summary>
        /// Efficiently deletes all data from any database tables used by the specified entity framework query. 
        /// </summary>
        /// <typeparam name="TContext">The DbContext Type on which to perform the delete.</typeparam>
        /// <typeparam name="TEntity">The Entity Type to which the query resolves.</typeparam>
        /// <param name="ctx">The DbContext on which to perform the delete.</param>
        /// <param name="deleteQuery">The query that references the tables you want to delete.</param>
        public static void BulkDelete<TContext, TEntity>(this TContext ctx, Func<TContext, IQueryable<TEntity>> deleteQuery) where TContext : DbContext {

            var findTables = new Regex(@"(?:FROM|JOIN)\s+(\[\w+\]\.\[\w+\])\s+AS");
            var qry = deleteQuery(ctx).ToString();

            // Get list of all tables mentioned in the query
            var tables = findTables.Matches(qry).Cast<Match>().Select(m => m.Result("$1")).Distinct().ToList();

            // Loop through all the tables, attempting to delete each one in turn
            var max = 30;
            var exception = (Exception)null;
            while (tables.Any() && max-- > 0) {

                // Get the next table
                var table = tables.First();

                try {
                    // Attempt the delete
                    ctx.Database.ExecuteSqlCommand(string.Format("DELETE FROM {0}", table));

                    // Success, so remove table from the list
                    tables.Remove(table);

                } catch (Exception ex) {
                    // Error, probably due to dependent constraint, save exception for later if needed.                    
                    exception = ex;

                    // Push the table to the back of the queue
                    tables.Remove(table);
                    tables.Add(table);
                }
            }

            // Error error has occurred, and cannot be resolved by deleting in a different 
            // order, then rethrow the exception and give up.
            if (max <= 0 && exception != null) throw exception;

        }
    }
}

I do it like this which seems to work fine. 我这样做似乎工作得很好。 Please let know if there is a reason why this is bad practice in any way. 如果有任何原因,这是不好的做法,请告诉我们。

        var customersToDelete = await ctx.Customers.Where(c => c.Email == "...").ToListAsync();

        foreach (var customerToDelete in customersToDelete)
        {
            ctx.Customers.Remove(customerToDelete);
        }

        await ctx.SaveChangesAsync();

I was experiencing the same problem with EF executing thousands of DELETE queries after SaveChanges call. 我在使用SaveChanges调用后执行数千个DELETE查询时遇到了同样的问题。 I wasn't sure that EntityFramework.Extensions commercial library would help me so I decided to implement bulk DELETE myself and came up with something similar to BG100's solution ! 我不确定EntityFramework.Extensions商业库会对我有所帮助所以我决定自己实现批量DELETE并提出类似于BG100解决方案的东西!

public async Task<List<TK>> BulkDeleteAsync(List<TK> ids)
{
    if (ids.Count < 1) {
        return new List<TK>();
    }

    // NOTE: DbContext here is a property of Repository Class
    // SOURCE: https://stackoverflow.com/a/9775744
    var tableName = DbContext.GetTableName<T>();
    var sql = $@"
        DELETE FROM {tableName}
        OUTPUT Deleted.Id
        // NOTE: Be aware that 'Id' column naming depends on your project conventions
        WHERE Id IN({string.Join(", ", ids)});
    ";
    return await @this.Database.SqlQuery<TK>(sql).ToListAsync();
}

If you have something like generic repository that should work for you just fine. 如果你有像通用存储库这样的东西应该适合你。 At least you could try to fit it into your EF infrastructure. 至少你可以尝试将它融入你的EF基础设施。

I also tweaked it a bit more and was able to execute queries on multiple chunks of entities. 我还调整了一点,并能够在多个实体块上执行查询。 It would help you if there are any restrictions of query size in your DB. 如果您的数据库中有任何查询大小限制,它会对您有所帮助。

const int ChunkSize = 1024;
public async Task<List<TK>> BulkDeleteAsync(List<TK> ids)
{
    if (ids.Count < 1) {
        return new List<TK>();
    }
    // SOURCE: https://stackoverflow.com/a/20953521/11344051
    var chunks = ids.Chunks(ChunkSize).Select(c => c.ToList()).ToList();
    var tableName = DbContext.GetTableName<T>();

    var deletedIds = new List<TK>();
    foreach (var chunk in chunks) {
        var sql = $@"
            DELETE FROM {tableName}
            OUTPUT Deleted.Id
            WHERE Id IN({string.Join(", ", chunk)});
        ";
        deletedIds.AddRange(DbContext.Database.SqlQuery<TK>(sql).ToListAsync());
    }
    return deletedIds;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM