Поделиться через


Полнотекстовый поиск в поставщике SQL Server для EF Core

SQL Server предоставляет возможности полнотекстового поиска , которые обеспечивают сложный поиск текста за пределами простых LIKE шаблонов. Полнотекстовый поиск поддерживает лингвистическое сопоставление, инфлекционные формы, поиск по близости и взвешенное ранжирование.

Поставщик SQL Server EF Core поддерживает как предикаты полнотекстового поиска (для фильтрации), так и таблично-значимые функции (для фильтрации с ранжированием).

Прежде чем использовать полнотекстовый поиск, необходимо создать полнотекстовый каталог в базе данных и полнотекстовый индекс столбцов, которые требуется выполнить поиск.

Замечание

Полнотекстовый каталог и управление индексами в миграциях было введено в EF Core 11.

Вы можете настроить полнотекстовые каталоги и индексы непосредственно в модели EF. При добавлении миграции EF создаст соответствующий SQL для создания (или изменения) каталога и индекса.

Сначала определите полнотекстовый каталог в модели, а затем настройте полнотекстовый индекс для типа сущности:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasFullTextCatalog("ftCatalog");

    modelBuilder.Entity<Article>()
        .HasFullTextIndex(a => a.Contents)
        .HasKeyIndex("PK_Articles")
        .OnCatalog("ftCatalog");
}

Метод HasKeyIndex() задает уникальный, не допускающий значения NULL, индекс с одним столбцом, используемый в качестве полнотекстового ключа таблицы (обычно индекс первичного ключа). OnCatalog() назначает полнотекстовый индекс определенному каталогу.

Можно также настроить несколько столбцов и дополнительные параметры, такие как языки столбцов и отслеживание изменений:

modelBuilder.Entity<Article>()
    .HasFullTextIndex(a => new { a.Title, a.Contents })
    .HasKeyIndex("PK_Articles")
    .OnCatalog("ftCatalog")
    .WithChangeTracking(FullTextChangeTracking.Manual)
    .HasLanguage("Title", "English")
    .HasLanguage("Contents", "French");

Полнотекстовый каталог также можно настроить в качестве каталога по умолчанию и с чувствительностью к акцентам:

modelBuilder.HasFullTextCatalog("ftCatalog")
    .IsDefault()
    .IsAccentSensitive(false);

Дополнительные сведения см. в документации по полнотекстовом поиску SQL Server.

Предикаты полнотекстового текста

EF Core поддерживает FREETEXT() и CONTAINS() предикаты, которые используются в Where() предложениях для фильтрации результатов.

FREETEXT()

FREETEXT() выполняет менее строгое сопоставление, поиск слов на основе их значения, включая инфлекционные формы (например, глагольные времена и множественные числа существительных):

var articles = await context.Articles
    .Where(a => EF.Functions.FreeText(a.Contents, "veggies"))
    .ToListAsync();

Это означает:

SELECT [a].[Id], [a].[Title], [a].[Contents]
FROM [Articles] AS [a]
WHERE FREETEXT([a].[Contents], N'veggies')

При необходимости можно указать термин языка:

var articles = await context.Articles
    .Where(a => EF.Functions.FreeText(a.Contents, "veggies", "English"))
    .ToListAsync();

CONTAINS()

CONTAINS() выполняет более точное сопоставление и поддерживает более сложные критерии поиска, включая префиксные термы, поиск по близости и взвешенные термы.

// Simple search
var articles = await context.Articles
    .Where(a => EF.Functions.Contains(a.Contents, "veggies"))
    .ToListAsync();

// Prefix search (words starting with "vegg")
var articles = await context.Articles
    .Where(a => EF.Functions.Contains(a.Contents, "\"vegg*\""))
    .ToListAsync();

// Phrase search
var articles = await context.Articles
    .Where(a => EF.Functions.Contains(a.Contents, "\"fresh vegetables\""))
    .ToListAsync();

Это означает:

SELECT [a].[Id], [a].[Title], [a].[Contents]
FROM [Articles] AS [a]
WHERE CONTAINS([a].[Contents], N'veggies')

Дополнительные сведения о синтаксисе запросов см. в CONTAINS()документации ПО SQL Server CONTAINS.

Функции с полнотекстовых табличными значениями

Замечание

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

Хотя приведенные выше предикаты полезны для фильтрации, они не предоставляют сведения о ранжировании. Табличные функции SQL Server FREETEXTTABLE() и CONTAINSTABLE() возвращают как соответствующие строки, так и оценку ранжирования, указывающую, насколько хорошо каждая строка соответствует поисковому запросу.

FreeTextTable()

FreeTextTable() — это версия FreeText()функции с табличным значением. Возвращается FullTextSearchResult<TEntity>, включающее как сущность, так и значение ранжирования:

var results = await context.Articles
    .Join(
        context.Articles.FreeTextTable<Article, int>("veggies", topN: 10),
        a => a.Id,
        ftt => ftt.Key,
        (a, ftt) => new { Article = a, ftt.Rank })
    .OrderByDescending(r => r.Rank)
    .ToListAsync();

foreach (var result in results)
{
    Console.WriteLine($"Article {result.Article.Id} with rank {result.Rank}");
}

Обратите внимание, что необходимо указать параметры универсального типа; Article соответствует типу сущности, для которого выполняется поиск, а int - это полнотекстовый ключ поиска, указанный при создании индекса, и который возвращается FREETEXTTABLE().

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

var results = await context.Articles
    .Join(
        context.Articles.FreeTextTable<Article, int>(a => a.Contents, "veggies"),
        a => a.Id,
        ftt => ftt.Key,
        (a, ftt) => new { Article = a, ftt.Rank })
    .OrderByDescending(r => r.Rank)
    .ToListAsync();

... или несколько столбцов:

var results = await context.Articles
    .FreeTextTable(a => new { a.Title, a.Contents }, "veggies")
    .Select(r => new { Article = r.Value, Rank = r.Rank })
    .OrderByDescending(r => r.Rank)
    .ToListAsync();

ContainsTable()

ContainsTable() — это версия Contains()функции с табличным значением, поддерживающая тот же сложный синтаксис поиска, а также предоставление сведений о ранжировании:

var results = await context.Articles
    .Join(
        context.Articles.ContainsTable<Article, int>( "veggies OR fruits"),
        a => a.Id,
        ftt => ftt.Key,
        (a, ftt) => new { Article = a, ftt.Rank })
    .OrderByDescending(r => r.Rank)
    .ToListAsync();

Ограничение результатов

Обе функции с табличным значением поддерживают topN параметр, чтобы ограничить количество результатов:

var results = await context.Articles
    .FreeTextTable(a => a.Contents, "veggies", topN: 10)
    .Select(r => new { Article = r.Value, Rank = r.Rank })
    .OrderByDescending(r => r.Rank)
    .ToListAsync();

Указание языка

Обе функции с табличным значением поддерживают указание термина языка для лингвистического сопоставления:

var results = await context.Articles
    .FreeTextTable(a => a.Contents, "veggies", languageTerm: "English")
    .Select(r => new { Article = r.Value, Rank = r.Rank })
    .ToListAsync();

Когда следует использовать предикаты и функции с табличным значением

Функция Предикаты (FreeText(), Contains()) Функции с табличным значением (FreeTextTable(), ContainsTable())
Предоставляет ранжирование ❌ Нет ✅ Да
Производительность для больших наборов результатов Лучше подходит для фильтрации Лучше для ранжирования и сортировки
Объединение с другими сущностями С помощью соединений Результат встроенной сущности
Использование в Where() предложении ✅ Да ❌ Нет (использовать в качестве источника)

Используйте предикаты, если необходимо просто фильтровать результаты на основе условий полнотекстового поиска. Используйте функции с табличным значением, если вам нужна информация о ранжировании, чтобы упорядочить результаты по релевантности или отображать оценки релевантности для пользователей.