.net EF代码首次从IQueryable中删除批次<;T>;?

.net EF代码首次从IQueryable中删除批次<;T>;?,.net,sql,entity-framework,ef-code-first,extension-methods,.net,Sql,Entity Framework,Ef Code First,Extension Methods,我知道这在LINQtoSQL中是可能的,我也看到了一些细节,让我相信在EF中也是可能的。有没有一个扩展可以做这样的事情: var peopleQuery = Context.People.Where(p => p.Name == "Jim"); peopleQuery.DeleteBatch(); 其中,DeleteBatch只需分离peopleQuery并创建一条SQL语句来删除所有适当的记录,然后直接执行查询,而不是将所有这些实体标记为删除并让它逐个执行。我想我在下面的代码中找到了

我知道这在LINQtoSQL中是可能的,我也看到了一些细节,让我相信在EF中也是可能的。有没有一个扩展可以做这样的事情:

var peopleQuery = Context.People.Where(p => p.Name == "Jim");

peopleQuery.DeleteBatch();
其中,
DeleteBatch
只需分离peopleQuery并创建一条SQL语句来删除所有适当的记录,然后直接执行查询,而不是将所有这些实体标记为删除并让它逐个执行。我想我在下面的代码中找到了类似的东西,但它立即失败,因为实例无法转换为ObjectSet。有人知道如何首先解决这个问题以使用EF代码吗?或者知道任何地方有这样的例子吗

public static IQueryable<T> DeleteBatch<T>(this IQueryable<T> instance) where T : class
{
    ObjectSet<T> query = instance as ObjectSet<T>;
    ObjectContext context = query.Context;

    string sqlClause = GetClause<T>(instance);
    context.ExecuteStoreCommand("DELETE {0}", sqlClause);

    return instance;
}

public static string GetClause<T>(this IQueryable<T> clause) where T : class
{
    string snippet = "FROM [dbo].[";

    string sql = ((ObjectQuery<T>)clause).ToTraceString();
    string sqlFirstPart = sql.Substring(sql.IndexOf(snippet));

    sqlFirstPart = sqlFirstPart.Replace("AS [Extent1]", "");
    sqlFirstPart = sqlFirstPart.Replace("[Extent1].", "");

    return sqlFirstPart;
}
公共静态IQueryable DeleteBatch(此IQueryable实例),其中T:class
{
ObjectSet query=作为ObjectSet的实例;
ObjectContext=query.context;
字符串sqlClause=GetClause(实例);
ExecuteStoreCommand(“删除{0}”,SQL子句);
返回实例;
}
公共静态字符串GetClause(此IQueryable子句),其中T:class
{
string snippet=“FROM[dbo].[”;
字符串sql=((ObjectQuery)子句).ToTraceString();
字符串sqlFirstPart=sql.Substring(sql.IndexOf(snippet));
sqlFirstPart=sqlFirstPart.Replace(“AS[Extent1]”,“”);
sqlFirstPart=sqlFirstPart.Replace(“[Extent1]”,“”);
返回第一部分;
}

我认为EF还不支持像delete这样的批处理操作。您可以执行原始查询:

 context.Database.ExecuteSqlCommand("delete from dbo.tbl_Users where isActive = 0"); 

实体框架不支持批处理操作。我喜欢代码解决问题的方式,但即使它完全符合您的要求(但对于ObjectContext API),它也是一个错误的解决方案

为什么它是错误的解决方案

它只在某些情况下起作用。在任何高级映射解决方案中,实体映射到多个表(实体拆分、TPT继承),它肯定不起作用。我几乎可以肯定,由于查询的复杂性,您可以找到另一种情况,它将不起作用

它使上下文和数据库不一致。这是针对DB执行的任何SQL的问题,但在这种情况下,SQL是隐藏的,使用您的代码的其他程序员可能会错过它。如果删除同时加载到上下文实例的任何记录,则实体将不会标记为已删除并从上下文中删除(除非将该代码添加到
DeleteBatch
方法中-如果删除的记录实际上映射到多个实体(表拆分),这将特别复杂)

最重要的问题是修改EF生成的SQL查询以及您对该查询所做的假设。您希望EF将查询中使用的第一个表命名为
Extent1
。是的,它现在确实使用该名称,但它是内部EF实现。它可以在EF的任何小更新中更改。构建自定义逻辑aro任何API的und内部都被认为是一种糟糕的做法

因此,您必须在SQL级别处理查询,以便可以直接调用@mreyeros显示的SQL查询,并避免此解决方案中的风险。您必须处理表和列的真实名称,但这是您可以控制的(您的映射可以定义它们)

如果你不认为这些风险是重要的,你可以对代码做一些小的改动,使它在dBaseAPI中工作:

public static class DbContextExtensions
{
    public static void DeleteBatch<T>(this DbContext context, IQueryable<T> query) where T : class
    {
        string sqlClause = GetClause<T>(query);
        context.Database.ExecuteSqlCommand(String.Format("DELETE {0}", sqlClause));
    }

    private static string GetClause<T>(IQueryable<T> clause) where T : class
    {
        string snippet = "FROM [dbo].[";

        string sql = clause.ToString();
        string sqlFirstPart = sql.Substring(sql.IndexOf(snippet));

        sqlFirstPart = sqlFirstPart.Replace("AS [Extent1]", "");
        sqlFirstPart = sqlFirstPart.Replace("[Extent1].", "");

        return sqlFirstPart;
    }
}

如果其他人正在寻找此功能,我已经使用了Ladislav的一些评论来改进他的示例,如果上下文已经在跟踪您删除的某个实体,它将调用它自己的delete。这不会修改任何记录,EF将其视为并发问题并引发异常。以下方法比原始方法慢,因为它必须首先查询要删除的项,但不会为eac编写单个删除查询h删除实体,这是真正的性能优势。它分离所有被查询的实体,因此,如果其中任何实体已被跟踪,它将知道不再删除它们

public static void DeleteBatch<T>(this DbContext context, IQueryable<T> query) where T : LcmpTableBase
{
    IEnumerable<T> toDelete = query.ToList();

    context.Database.ExecuteSqlCommand(GetDeleteCommand(query));

    var toRemove = context.ChangeTracker.Entries<T>().Where(t => t.State == EntityState.Deleted).ToList();

    foreach (var t in toRemove)
        t.State = EntityState.Detached;
}
publicstaticvoiddeletebatch(此DbContext上下文,IQueryable查询),其中T:LcmpTableBase
{
IEnumerable toDelete=query.ToList();
ExecuteSqlCommand(GetDeleteCommand(query));
var toRemove=context.ChangeTracker.Entries()。其中(t=>t.State==EntityState.Deleted)。ToList();
foreach(toRemove中的var t)
t、 State=EntityState.Detached;
}
我还将这一部分改为使用正则表达式,因为我发现FROM部分附近有不确定数量的空格。我还在其中留下“[Extent1]”,因为以原始方式编写的删除查询无法处理带有内部联接的查询:

public static string GetDeleteCommand<T>(this IQueryable<T> clause) where T : class
{
    string sql = clause.ToString();

    Match match = Regex.Match(sql, @"FROM\s*\[dbo\].", RegexOptions.IgnoreCase);

    return string.Format("DELETE [Extent1] {0}", sql.Substring(match.Index));
}
public静态字符串GetDeleteCommand(此IQueryable子句),其中T:class
{
字符串sql=子句.ToString();
Match Match=Regex.Match(sql,@“FROM\s*\[dbo\]”,RegexOptions.IgnoreCase);
返回string.Format(“DELETE[Extent1]{0}”,sql.Substring(match.Index));
}

我知道它们目前不受支持,但我希望我可以像过去那样直接从表达式中生成delete命令。我100%同意你的观点,但考虑到以官方方式执行该命令会对性能造成严重影响,这是我们项目中可以接受的风险。感谢你的回复安塞!
public static string GetDeleteCommand<T>(this IQueryable<T> clause) where T : class
{
    string sql = clause.ToString();

    Match match = Regex.Match(sql, @"FROM\s*\[dbo\].", RegexOptions.IgnoreCase);

    return string.Format("DELETE [Extent1] {0}", sql.Substring(match.Index));
}