К сожалению, это самоотражающееся отношение, и каскадное удаление не может быть использовано из-за проблемы «нескольких каскадных путей» - ограничения базы данных SqlServer (и, возможно, другой) (у Oracle такой проблемы нет).
Лучший способ обработки в базах данных, которые не поддерживают «несколько каскадных путей», - это использовать триггер базы данных («вместо удаления»).
Но, допустим, мы хотим обработать его с помощью клиентского кода в EF Core. Вопрос в том, как эффективно загрузить рекурсивную древовидную структуру (еще одна непростая задача в EF Core из-за отсутствия поддержки рекурсивных запросов).
Проблема с вашим кодом состоит в том, что он использует глубина вначале алгоритм, который выполняет много запросов к базе данных. Более подходящий и эффективный способ - использовать алгоритм дыхание вначале - простыми словами, загружая предметы на уровень . Таким образом, количество запросов к базе данных будет максимальной глубиной в дереве, которая будет намного меньше, чем количество элементов.
Один из способов реализации - применить запрос с начальным фильтром, а затем использовать SelectMany
для получения следующего уровня (каждый SelectMany
добавляет соединение к предыдущему запросу). Процесс заканчивается, когда запрос не возвращает данные:
public static async Task ForceDelete(int ID, ProductContext context)
{
var items = new List<ProductDefinition>();
// Collect the items by level
var query = context.ProductDefinitions.Where(e => e.ID == ID);
while (true)
{
var nextLevel = await query
.Include(e => e.Supplier)
.ToListAsync();
if (nextLevel.Count == 0) break;
items.AddRange(nextLevel);
query = query.SelectMany(e => e.ProductDefinitions);
}
foreach (var item in items)
item.Supplier.Edited = true;
context.RemoveRange(items);
await context.SaveChangesAsync();
}
Обратите внимание, что выполненные запросы активно загружают связанный Supplier
, поэтому его можно легко обновить.
После того, как элементы собраны, они просто помечаются для удаления с помощью метода RemoveRange
. Порядок не имеет значения, потому что EF Core все равно будет применять команды в соответствии с порядком зависимости.
Еще один способ сбора предметов - использовать ID
s с предыдущего уровня в качестве фильтра (SQL IN
):
// Collect the items by level
Expression<Func<ProductDefinition, bool>> filter = e => e.ID == ID;
while (true)
{
var nextLevel = await context.ProductDefinitions
.Include(e => e.Supplier)
.Where(filter)
.ToListAsync();
if (nextLevel.Count == 0) break;
items.AddRange(nextLevel);
var parentIds = nextLevel.Select(e => e.ID);
filter = e => parentIds.Contains(e.ParentProductDefinitionId.Value);
}
Мне больше нравится первый. Недостатком является то, что EF Core генерирует огромные псевдонимы имен таблиц, а также может привести к некоторому ограничению числа соединений SQL в случае большой глубины. Последний не имеет ограничения по глубине, но может иметь проблемы с большим предложением IN
. Вы должны проверить, какой из них больше подходит для вашего случая.