Подтвердить что ты не робот

"Составляемый" полнотекстовый поиск с первой моделью кода

ОБНОВЛЕНИЕ 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, это поможет?

4b9b3361

Ответ 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 или менее, второй вариант лучше, чем первый, но не самый оптимальный.