Есть небольшая программа. Сделана в целом по принципам "внедрения зависимостей" и "чистой архитектуры".
Что надо сделать.
Бизнес-операция (usecase) должна выполнить необычный поиск по Репозиторию неких Сущностей (в нашем случае, сущности — это юридические лица).
Поиск выполняется по нескольким "критериям" поиска. Каждый "критерий" — это произвольный набор полей Сущности и их значения ("Поле" — "Значение").
Например:
— Критерий-1: "краткое название" = "Ромашка", "ИНН" = "11223344556600"
— Критерий-2: "юридическое наименование" = "ООО Ромашка", "регион регистрации" = "Москва"
— Критерий-3: "ИНН" = "55667788443322", "КПП" = "6010200001"
Одно и тоже юрлицо может быть найдено по разным критериям. Например, и по первому критерию и по второму критерию.
Особенность поиска в том, что после поиска по какому-то Критерию из дальнейшего поиска исключаются сущности, найденные по этому критерию.
То есть алгоритм должен быть примерно таким:
var repository = new Repository();
var criteria_array = new Criteria[];
// заполнение массива Критериевforeach (var criteria in criteria_array)
{
var found_entities = repository.FindByCriteria(criteria);
...
// исключаем найденные сущности из дальнейшего поиска на следующем витке цикла - как???
...
// добавляем найденные сущности в отдельную кеш-таблицу, в которой есть дополнительная колонка "Критерий"
// для каждой сущности запоминаем критерий, по которому была найдена эта сущность
cashe_table.Add(found_entities, criteria);
}
process_list = cashe_table.GetAll();
foreach (var entity in process_list)
{
if (entity.search_criteria.dostovernost = 1)
algorithm1(entity);
elseif (entity.search_criteria.dostovernost = 2)
algorithm2(entity);
....
}
Пример. Предположим, в БД есть юридическое лицо со следующими полями:
— "юридическое наименование" = "ООО Ромашка"
— "краткое название" = "Ромашка"
— "регион регистрации" = "Москва"
— "ИНН" = "11223344556600".
Это юридическое лицо может быть найдено по Критерию-1 и по Критерию-2.
Однако, нам нужно, чтобы после нахождения по Критерию-1, это юрлицо не попало в выборку по Критерию-2.
То есть, найденные юрлица исключаются из следующих поисков. То есть это постоянно "сужающийся" поиск.
Объясню зачем нужен такой "сложный" поиск.
Грубо — в зависимости от того критерия, по которому отобраны сущности, выполняются разные обработки над выборками.
У каждого критерия есть признак "вес достоверности".
Например, самый "достоверный" — это Критреий-1. Юрлица, найденные по этому критерию являются "достоверными" и они обрабатываются по одному алгоритму.
Юрлица, найденные по следующему критерию, менее достоверные, их надо обрабатывать по алгоритму-2. И т.д.
Проблема в том, что результаты поиска по каждому критерию пересекающиеся.
То есть одно и тоже юрлицо может попасть в выборку и по Критерию-1 и по Критерию-2 и по другим Критериям.
Но обработано юрлицо должно быть по наиболее "достоверному" Критерию и соответствующему алгоритму.
То есть, если юрлицо попало в выборку по более "достоверному" Критерию-1, то оно должно быть обработано по Алгоритму-1. Нет смысла рассматривать это юрлицо по другим Критериям. Поэтому из поиска по другим Критериям это юрлицо надо как-то исключить.
Вопрос: как при поиске по следующему в массиве Критерию-(n) "отсечь" из результата поиска те сущности, которые были найдены при поиске по предыдущим Критериям?
Была мысль сделать это "топорно": сначала читаем все сущности во временную таблицу, в которой есть поле-"флажок".
Поиск по Критериям выполняем по этой временной таблице. И после поиска по каждому Критерию для найденных сущностей "включаем" этот "флажок".
При поиске по каждому критерию добавляем в условия поиска кроме самого критерия дополнительное условие "флажок" = "выключен".
Но нам этот подход кажется сомнительным.
Посоветуйте плиз как сделать грамотно в русле разделения слоев и "чистой архитектуры".
Не очень понятно что нужно. Если нужно это долго где-то хранить, то делаем сущность типа SearchResult, SearchSesstion и т.д. и в ней храним то что нам надо.
Программа – это мысли спрессованные в код
Re[2]: Репозиторий. Как сделать поиск с исключением
Дополнил первый свой пост уточнением:
Z>Одно и тоже юрлицо может быть найдено по разным критериям. Например, и по первому критерию и по второму критерию.
... и примером:
Z>Пример. Предположим, в БД есть юридическое лицо со следующими полями: Z>- "юридическое наименование" = "ООО Ромашка" Z>- "краткое название" = "Ромашка" Z>- "регион регистрации" = "Москва" Z>- "ИНН" = "11223344556600". Z>Это юридическое лицо может быть найдено по Критерию-1 и по Критерию-2. Z>Однако, нам нужно, чтобы после нахождения по Критерию-1, это юрлицо не попало в выборку по Критерию-2. Z>То есть, найденные юрлица исключаются из следующих поисков. То есть это постоянно "сужающийся" поиск.
Надеюсь, теперь понятнее.
Q>Если нужно это долго где-то хранить, то делаем сущность типа SearchResult, SearchSesstion и т.д. и в ней храним то что нам надо.
Про хранение да, примерно так и думали. Обработка найденных сущностей зависит от того, по какому критерию они были найдены.
Сущность SearchResult будет примерно такой структуры:
class SearchResult
{
_entity Entity;
_criteria Criteria;
}
Все равно не очень понятно зачем это надо. Обычно такая фильтрация делается просто составным фильтром. Никаких "промежуточных результатов" не нужно.
Большинство библиотек компонентов это поддерживают. Т.е. в чем смысл разбивки, почему просто не сделать?
Обычный подход — именованные фильтры (view)
Здравствуйте, zelenprog, Вы писали:
Z>Посоветуйте плиз как сделать грамотно в русле разделения слоев и "чистой архитектуры".
Мне кажется, задача полностью делегируется слою БД, и план SQL-запроса оптимизируется по костам БД-инженером, это уже задача сопровожденцев вашей БД.
С точки зрения чистой архитектуры у вас есть обычный DAO-слой и Criteria API, который либо инкапсулирует в виде готовых промышленных библиотек (типа Hibernate, где уже есть внутренние оптимизаторы и кэши, при этом конечно имеет смысл подключить кэш второго уровня), либо делегирует подобные оптимизации БД.
Re[2]: Репозиторий. Как сделать поиск с исключением
В зависимости от того критерия, по которому отобраны сущности, выполняются разные обработки над выборками.
У каждого критерия есть признак "вес достоверности".
Например, самый "достоверный" — это Критреий-1. Юрлица, найденные по этому критерию являются "достоверными" и они обрабатываются по одному алгоритму.
Юрлица, найденные по следующему критерию, менее достоверные, их надо обрабатывать по алгоритму-2. И т.д.
Проблема в том, что результаты поиска по каждому критерию пересекающиеся.
То есть одно и тоже юрлицо может попасть в выборку и по Критерию-1 и по Критерию-2 и по другим Критериям.
Но обработано юрлицо должно быть по наиболее "достоверному" Критерию и соответствующему алгоритму.
То есть, если юрлицо попало в выборку по более "достоверному" Критерию-1, то оно должно быть обработано по Алгоритму-1. Нет смысла рассматривать это юрлицо по другим Критериям. Поэтому из поиска по другим Критериям это юрлицо надо как-то исключить.
bnk>Обычно такая фильтрация делается просто составным фильтром. Никаких "промежуточных результатов" не нужно. bnk>Большинство библиотек компонентов это поддерживают. Т.е. в чем смысл разбивки, почему просто не сделать? ...
Много разных полей. Некоторые поля не заполнены. Поиск можно выполнять по произвольным полям. Поля для поиска, их значения и вес "достоверности" можно настраивать.
Например, можно отобрать всех юрлиц, у которых оборот за прошлый год >= 20 млн.руб.
Идею c фильтрами понял.
Но тогда придется писать такие условия в запросах:
— 1-й поиск по Критерию-1: если "Критерий1", то...
— 2-й поиск по Критерию-2: если "Критерий2" и НЕ "Критерий1"
— 3-й поиск по Критерию-3: если "Критерий3" и НЕ "Критерий1" и НЕ "Критерий2"
...
— n-й поиск по Критерию-n: если "Критерий-n" и НЕ "Критерий1" и НЕ "Критерий2" и ... и НЕ "Критерий-n-1"
Вроде бы должно сработать. Хотя как-то сильно муторно получается.
Re[2]: Репозиторий. Как сделать поиск с исключением
Здравствуйте, zelenprog, Вы писали:
G>>...план SQL-запроса оптимизируется по костам БД-инженером... Z>Не понял что такое "костам"?
"Кост" — это "стоимость" каждого этапа выполнения SQL-запроса (в условных попугаях, отражающих перформанс БД — скорость и размер памяти).
Например, если в запросе есть выборка из таблицы по полю (фильтр), которое не имеет индекса, то "стоимость" такой операции высокая, т.к. движок БД вынужден делать полный перебор O(n) таблицы для применения данного фильтра.
Навешивание индекса на поле снижает "кост" и по скорости, т.к. теперь движок делает не полный перебор O(n), а частичный, например, по B-tree или по хэшу.
Вручную анализировать сложный SQL-запрос таким образом тяжело, поэтому любой уважающий себя БД-движок имеет команду explain, которая выводит кост-план конкретного запроса и позволяет сразу видеть узкие места и подтюнить БД. Эту работу обычно выполняет БД-инженер.
Выполнять поэтапную фильтрацию на стороне бэкэнда — так в реляционных БД не делают, это очень ресурсоёмко, обычно fleunt-цепока вызовов Criteria API на стороне бэка (DAO-слой ) формирует сложный SQL-запрос, который улетает в БД и обратно возвращается уже отфильтрованное.
Здравствуйте, zelenprog, Вы писали:
Z>Объясню зачем нужен такой "сложный" поиск.
Z>Грубо — в зависимости от того критерия, по которому отобраны сущности, выполняются разные обработки над выборками. Z>У каждого критерия есть признак "вес достоверности". Z>Например, самый "достоверный" — это Критреий-1. Юрлица, найденные по этому критерию являются "достоверными" и они обрабатываются по одному алгоритму. Z>Юрлица, найденные по следующему критерию, менее достоверные, их надо обрабатывать по алгоритму-2. И т.д.
Z>Проблема в том, что результаты поиска по каждому критерию пересекающиеся. Z>То есть одно и тоже юрлицо может попасть в выборку и по Критерию-1 и по Критерию-2 и по другим Критериям. Z>Но обработано юрлицо должно быть по наиболее "достоверному" Критерию и соответствующему алгоритму. Z>То есть, если юрлицо попало в выборку по более "достоверному" Критерию-1, то оно должно быть обработано по Алгоритму-1. Нет смысла рассматривать это юрлицо по другим Критериям. Поэтому из поиска по другим Критериям это юрлицо надо как-то исключить.
Z>Вопрос: как при поиске по следующему в массиве Критерию-(n) "отсечь" из результата поиска те сущности, которые были найдены при поиске по предыдущим Критериям?
1) Уже написали про составной критерий, а чтобы узнать по какому конкретно критерию выла выбрана сущность, в SQL можно использовать в селекте конструкцию case/when/then, где сравнения критериев указаны в порядке убывания "веса", в LINQ аналог — тернарный условный оператор (?: ) в селекте, тут главнео чтобы используемый linq-провайдер поддерживал генерацию case/when/then по этому оператору. Т.е. критерии должны быть продублированны как в where, так и в select case/when/then (?: ).
Имхо это самый эффектиный способ, елси кол-во критерией не очень большое (десятки, может сотни, оптимальное кол-во определять в результате тестирования).
2) Выборка по одному критерию в цикле (критерии отсортированны по убывания "веса"):
На каждой итерации выбираем только ID (PK) сущностей соотв. критерию, складываем напр. в HashTable (ключ ID, значение — "вес") — добавляем только несуществующие в хэштаблице.
Затем для накопленных ID читаем сами сущности.
3) Выборка по одному критерию в цикле (критерии отсортированны по убывания "веса"), SQL база/временная таблица:
Создаём временную таблицу с уникальным индексом по ID и флагом ignore duplicates (база должна поддерживать такую фичу), одним SQL запросом (select/insert) вставляем сущности/вес в эту таблицу. Тут опять же, используемый linq-провайдер должен поддерживать генерацию такого sql (linq2db умеет).
Затем из этой таблицы читаем сами сущности и их вес.
upd: Для эффективности выборки по критериям таблицы должны иметь соотв. индексы дял используемых в критериях полей.
Z>Посоветуйте плиз как сделать грамотно в русле разделения слоев и "чистой архитектуры".
Всё выше описаннео расположить в репозитории — чище некуда
Здравствуйте, zelenprog, Вы писали:
Z>Посоветуйте плиз как сделать грамотно в русле разделения слоев и "чистой архитектуры".
Я бы отдал в репозиторий полностью обязанность разбираться с поиском И достоверностями. А use case получит уже очищенные данные
Т.е. repository.FindMyObjects(criteriaList) должен вернуть и сами объекты и их достоверность
Чтобы избежать повторной выдачи:
— можно реализовать в коде — например, сохранять уже найденные объекты в Set, потом проверять их и не выдавать повторно в результат
— можно реализовать в БД — выдать в БД запрос UNION сразу по нескольким критериям, и потом оконной функцией отобрать объекты у которых "наименьшая" достоверность
var repository = new Repository();
var criteria_array = new Criteria[];
// заполнение массива Критериевvar alreadySearchedCriteria = new FalseCriteria();
foreach (var criteria in criteria_array)
{
var found_entities = repository.FindByCriteria(criteria.And(alreadySearchedCriteria.Not());
...
// исключаем найденные сущности из дальнейшего поиска на следующем витке цикла - вот так:
alreadySearchedCriteria = alreadySearchedCriteria.Or(criteria);
.
// добавляем найденные сущности в отдельную кеш-таблицу, в которой есть дополнительная колонка "Критерий"
// для каждой сущности запоминаем критерий, по которому была найдена эта сущность
cashe_table.Add(found_entities, criteria);
}
process_list = cashe_table.GetAll();
foreach (var entity in process_list)
{
if (entity.search_criteria.dostovernost = 1)
algorithm1(entity);
elseif (entity.search_criteria.dostovernost = 2)
algorithm2(entity);
....
}
Уйдемте отсюда, Румата! У вас слишком богатые погреба.