Код EF сначала удалить партию из IQueryable <T>? - PullRequest
5 голосов
/ 16 декабря 2011

Я знаю, что это возможно в LINQ-to-SQL, и я видел фрагменты, которые наводят меня на мысль, что это возможно в EF.Есть ли расширение, которое может сделать что-то вроде этого:

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

peopleQuery.DeleteBatch();

Где DeleteBatch просто разбирает peopleQuery и создает один SQL-оператор для удаления всех соответствующих записей, а затем выполняет запрос напрямую вместо этогопометить все эти объекты для удаления и сделать это один за другим.Мне показалось, что я нашел что-то подобное в приведенном ниже коде, но это немедленно завершается ошибкой, потому что экземпляр не может быть приведен к ObjectSet.Кто-нибудь знает, как это исправить для работы с EF Code First?Или знаете где-нибудь, где есть пример того, как это делается?

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

Ответы [ 3 ]

5 голосов
/ 17 декабря 2011

Entity Framework не поддерживает пакетные операции.Мне нравится, как код решает проблему, но даже если он делает именно то, что вы хотите (но для ObjectContext API), это неправильное решение.

Почему это неправильное решение?

Работает только в некоторых случаях.Он определенно не будет работать ни в одном расширенном решении для сопоставления, где сущность сопоставляется с несколькими таблицами (разбиение сущностей, наследование TPT).Я почти уверен, что вы можете найти другие ситуации, в которых он не будет работать из-за сложности запроса.

Он не поддерживает контекст и базу данных.Это проблема любого SQL, выполняемого для БД, но в этом случае SQL скрыт, и другой программист, использующий ваш код, может пропустить его.Если вы удаляете любую запись, которая одновременно загружается в экземпляр контекста, сущность не будет помечена как удаленная и удаленная из контекста (если вы не добавите этот код в свой метод DeleteBatch - это будет особенно сложно, если удалена записьфактически сопоставляется с несколькими сущностями (разбиение таблицы)).

Наиболее важной проблемой является изменение сгенерированного EF SQL-запроса и предположений, которые вы делаете для этого запроса.Вы ожидаете, что EF назовет первую таблицу, использованную в запросе, как Extent1.Да, он действительно использует это имя сейчас, но это внутренняя реализация EF.Это может измениться в любом незначительном обновлении EF.Построение собственной логики вокруг внутренних компонентов любого API считается плохой практикой.

В результате вам уже приходится работать с запросом на уровне SQL, чтобы вы могли вызывать запрос SQL напрямую, как показывал @mreyeros, и избегать рисков вэто решение.Вам придется иметь дело с реальными именами таблиц и столбцов, но это то, что вы можете контролировать (ваше отображение может определять их).

Если вы не считаете эти риски значительными, вы можете внести небольшие изменения вкод для работы в DbContext API:

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

Теперь вы будете вызывать пакетное удаление следующим образом:

context.DeleteBatch(context.People.Where(p => p.Name == "Jim"));
1 голос
/ 19 декабря 2011

В случае, если кто-то еще ищет эту функциональность, я использовал некоторые комментарии Ладислава, чтобы улучшить его пример. Как он сказал, в исходном решении, когда вы вызываете SaveChanges(), если контекст уже отслеживает одну из удаленных вами сущностей, он будет вызывать собственное удаление. Это не изменяет никаких записей, и EF считает это проблемой параллелизма и выдает исключение. Приведенный ниже метод медленнее, чем оригинал, поскольку он должен сначала запросить элементы для удаления, но он не будет писать по одному запросу на удаление для каждого удаленного объекта, что является реальным преимуществом производительности. Он отсоединяет все запрошенные объекты, поэтому, если какие-либо из них уже были отслежены, он будет знать, что больше не должен их удалять.

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

Я также изменил эту часть для использования регулярного выражения, поскольку обнаружил, что рядом с частью FROM имеется неопределенное количество пробелов. Я также оставил там «[Extent1]», потому что запрос DELETE, написанный оригинальным способом, не мог обрабатывать запросы с INNER JOINS:

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));
}
1 голос
/ 16 декабря 2011

Я не верю, что пакетные операции, такие как удаление, еще поддерживаются EF. Вы можете выполнить необработанный запрос:

 context.Database.ExecuteSqlCommand("delete from dbo.tbl_Users where isActive = 0"); 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...