"Составляемый" полнотекстовый поиск с первой моделью кода
ОБНОВЛЕНИЕ 18 сентября 2013
Похоже, что нет простого способа сделать это. Я поддерживаю решение, которое предполагает некоторое расширение для Entity Framework.
Если вы хотите увидеть эти функции в Entity Framework, проголосуйте за них на голосовом сайте пользователя, возможно здесь и здесь
Есть несколько аналогичных вопросов по SO, но я не могу найти вопрос новый и достаточно похожий, чтобы получить ответ, который я ищу.
Если это похоже на информационную перегрузку, перейдите к В резюме.
Фон
Я пишу службу WebApi REST, чтобы выставить некоторые ранее существовавшие данные через конечную точку OData. Я использую EntitySetContoller<TEntity, TKey>
, чтобы сделать для меня всю работу. Помимо стандартных параметров OData которые маршрутизируются и транслируются базовым классом, я добавил некоторые пользовательские параметры, чтобы обеспечить определенную функциональность для мой контроллер.
Мой сервер базы данных - это MS SQL Server с полным текстовым индексом в столбце [BigText] NVarChar[4000]
таблицы [SomeEntity]
.
У меня есть одно ограничение, Я должен использовать модель Code First.
// Model POCO
public class SomeEntity
{
public int Id { get; set; }
public string BigText { get; set; }
}
// Simple Controller
public class SomeEntityController : EntitySetController<SomeEntity, int>
{
private readonly SomeDbContext context = new SomeDbContext();
public override IQueryable<SomeEntity> Get()
{
var parameters = Request.GetQueryNameValuePairs()
.ToDictionary(p => p.Key, p => p.Value);
if (parameters.ContainsKey("BigTextContains")
(
var searchTerms = parameters["BigTextContains"];
// return something special ...
)
return this.context.SomeEntities;
}
// ... The rest is omitted for brevity.
}
Проблема
Как реализовать часть // return something special ...
моего примера?
Очевидно, что niave
return this.context.SomeEntities.Where(e =>
e.BigText.Contains(searchTerm));
полностью неверен, он состоит из предложения WHERE
типа
[BigText] LIKE '%' + @searchTerm + '%'
Это не использует полнотекстовый поиск, поэтому не поддерживает сложные поисковые запросы и, наоборот, выполняет ужасно.
Этот подход,
return this.context.SomeEntities.SqlQuery(
"SELECT E.* FROM [dbo].[SomeEntity] E " +
"JOIN CONTAINSTABLE([SomeEntity], [BigText], @searchTerm) FTS " +
" ON FTS.[Key] = E.[Id]",
new object[] { new SqlParameter("@searchTerm", searchTerm) })
.AsQueryable();
Выглядит многообещающе, на самом деле использует полнотекстовый поиск и вполне функциональный. Однако вы заметите, что DbSqlQuery
, тип, возвращаемый функцией SqlQuery
, не реализует IQueryable
. Здесь он принуждается к правильному типу возврата с расширением AsQueryable()
, но это нарушает "цепочку композиции". Единственный оператор, который будет выполняться на сервере, - это тот, который указан в приведенном выше коде. Любые дополнительные предложения, указанные в URL-адресе OData, будут обслуживаться на веб-сервере, поддерживающем API, без использования индексов и специализированных функций, основанных на базе базы данных.
В резюме
Каков наиболее целесообразный способ доступа к полнотекстовому поиску в MS SQL Server CONTAINSTABLE
с помощью модели Entity Framework 5 Code First и приобретения "композитный" результат?
Нужно ли писать собственный IQueryProvider
? Можно ли каким-то образом расширить EF?
Я не хочу использовать Lucene.Net, я не хочу использовать созданную базу данных. Возможно, я мог бы добавить дополнительные пакеты или подождать EF6, это поможет?
Ответы
Ответ 1
Это не идеально, но вы можете выполнить то, что вам нужно, с помощью двух вызовов в базе данных.
Первый вызов будет извлекать список совпадающих ключей из CONTAINSTABLE, а затем вторым вызовом будет ваш составной запрос с использованием идентификаторов, которые вы вернули с первого вызова.
//Get the Keys from the FTS
var ids = context.Database.SqlQuery<int>(
"Select [KEY] from CONTAINSTABLE([SomeEntity], [BigText], @searchTerm)",
new object[] { new SqlParameter("@searchTerm", searchTerm) });
//Use the IDs as an initial filter on the query
var composablequery = context.SomeEntities.Where(d => ids.Contains(d.Id));
//add on whatever other parameters were captured to the 'composablequery' variable
composablequery = composablequery.Where(.....)
Ответ 2
У меня была эта же проблема в последнее время:
EF 5 Code First FTS Queriable
Позвольте мне продлить это сообщение.
-
Ваш первый вариант был моим первым, используя SqlQuery
Мне также нужно было сделать больше фильтрации, поэтому вместо того, чтобы писать полный sql файл, я использовал QueryBuilder, к которому я вносил некоторые изменения и добавлял больше функций, соответствующих моим потребностям (я мог бы загрузить его где-нибудь, если нужно):
QueryBuilder
-
После того, как я нашел другую идею, которую я реализовал.
Кто-то уже упоминает об этом здесь, то есть использовать SqlQuery, который вернет HashSet идентификаторов и что вы можете использовать его в EF-запросах с помощью Contains.
Это лучше, но не наиболее оптимально, так как вам нужно 2 запроса и список идентификаторов в памяти.
Пример:
public IQueryable<Company> FullTextSearchCompaniesByName(int limit, int offset, string input, Guid accountingBureauId, string orderByColumn)
{
FtsQueryBuilder ftsQueryBuilder = new FtsQueryBuilder();
ftsQueryBuilder.Input = FtsQueryBuilder.FormatQuery(input);
ftsQueryBuilder.TableName = FtsQueryBuilder.GetTableName<Company>();
ftsQueryBuilder.OrderByTable = ftsQueryBuilder.TableName;
ftsQueryBuilder.OrderByColumn = orderByColumn;
ftsQueryBuilder.Columns.Add("CompanyId");
if (accountingBureauId != null && accountingBureauId != Guid.Empty)
ftsQueryBuilder.AddConditionQuery<Guid>(Condition.And, "" , @"dbo.""Company"".""AccountingBureauId""", Operator.Equals, accountingBureauId, "AccountingBureauId", "");
ftsQueryBuilder.AddConditionQuery<bool>(Condition.And, "", @"dbo.""Company"".""Deleted""", Operator.Equals, false, "Deleted", "");
var companiesQuery = ftsQueryBuilder.BuildAndExecuteFtsQuery<Guid>(Context, limit, offset, "Name");
TotalCountQuery = ftsQueryBuilder.Total;
HashSet<Guid> companiesIdSet = new HashSet<Guid>(companiesQuery);
var q = Query().Where(a => companiesIdSet.Contains(a.CompanyId));
return q;
}
-
Однако в EF 6 теперь есть что-то, называемое Interceptors, которое может быть использовано для реализации Queriable FTS, и оно довольно простое и общее (последнее сообщение):
Перехватчики EF 6 для FTS.
Я тестировал это, и он отлично работает.
!! ЗАМЕЧАНИЕ: EF-код. Прежде всего, даже с версией 6, не поддерживает пользовательские хранимые процедуры.
Есть только некоторые из предопределенных операций CUD, если я хорошо их понял:
Code First Вставить/Обновить/Удалить сохраненную процедуру сопоставления, поэтому с ней не может быть сделано.
Заключение: если вы можете использовать EF 6 для третьих вариантов, это дает все, что вам нужно.
Если вы застряли с EF 5 или менее, второй вариант лучше, чем первый, но не самый оптимальный.