教程:使用 .NET SDK 从多个数据源编制索引

Azure AI 搜索可以导入、分析来自多个数据源的数据,并将其编制到单个合并的搜索索引中。

本 C# 教程使用适用于 .NET 的 Azure SDK 中的 Azure.Search.Documents 客户端库来对 Azure Cosmos DB 实例中的示例酒店数据编制索引,并将其与从 Azure Blob 存储文档中提取的酒店房间详细信息合并。 结果将是一个包含酒店文档的组合酒店搜索索引,其中房间是一种复杂数据类型。

你将在本教程中执行以下任务:

  • 上传示例数据和创建数据源
  • 标识文档密钥
  • 定义和创建索引
  • 为来自 Azure Cosmos DB 的酒店数据编制索引
  • 合并来自 Blob 存储的酒店房间数据

如果没有 Azure 订阅,请在开始前创建一个试用版订阅

概述

本教程使用 Azure.Search.Documents 来创建和运行多个索引器。 在本教程中,你将设置两个 Azure 数据源,以便你可以配置一个索引器,该索引器从这两个数据源中提取数据以填充单个搜索索引。 这两个数据集必须有一组相同的值才支持合并。 在此示例中,该字段是一个 ID。 只要有一个公共字段支持映射,索引器就可以合并来自不同资源的数据:来自 Azure SQL 的结构化数据、来自 Blob 存储的非结构化数据,或者 Azure 上支持的数据源的任意组合。

可以在以下项目中找到本教程中代码的完成版本:

先决条件

注意

可在本教程中使用免费搜索服务。 免费层限制为三个索引、三个索引器和三个数据源。 本教程每样创建一个。 在开始之前,请确保服务中有足够的空间可接受新资源。

1 - 创建服务

本教程使用 Azure AI 搜索进行索引编制和查询,对一个数据集使用 Azure Cosmos DB,对第二个数据集使用 Azure Blob 存储。

如果可能,请在同一区域和资源组中创建所有服务,使它们相互靠近并易于管理。 在实践中,服务可位于任意区域。

此示例使用两个小数据集,其中包含七个虚构酒店的数据。 一个数据集描述酒店本身,将加载到 Azure Cosmos DB 数据库中。 另一个包含酒店房间的详细信息,并以七个单独的 JSON 文件的形式提供,将上传到 Azure Blob 存储中。

Azure Cosmos DB 入门

  1. 登录到 Azure 门户,然后导航到自己的 Azure Cosmos DB 帐户的“概述”页。

  2. 依次选择“数据资源管理器”、“新建数据库”。

    创建新数据库

  3. 输入名称 hotel-rooms-db。 对于剩余的设置,请接受默认值。

    配置数据库

  4. 创建新容器。 使用刚刚创建的现有数据库。 输入hotels 作为容器名称,输入 /HotelId 作为分区键。

    添加容器

  5. 选择“hotels”下的“项”,然后选择命令栏上的“上传项”。 导航到项目文件夹中的 cosmosdb/HotelsDataSubset_CosmosDb.json 文件并将其选中。

    上传到 Azure Cosmos DB 集合

  6. 使用“刷新”按钮来刷新酒店集合中的项的视图。 此时应会列出七个新数据库文档。

  7. 将连接字符串从“密钥”页复制到“记事本”中。 在稍后的步骤中,需要将此值用于“appsettings.json”。 如果未使用建议的数据库名称“hotel-rooms-db”,请同时复制数据库名称。

Azure Blob 存储

  1. 登录到 Azure 门户,导航到你的 Azure 存储帐户,选择“Blob”,然后选择“+ 容器”。

  2. 创建 blob 容器,名为“hotel-rooms” ,用于存储示例酒店房间 JSON 文件。 可将“公共访问级别”设为任何有效值。

    创建一个 blob 容器

  3. 创建容器后,将其打开,然后在命令栏中选择“上传” 。 导航到包含示例文件的文件夹。 选择所有这些文件,然后选择“上传”。

    上传文件

  4. 将存储帐户名和连接字符串从“访问密钥”页复制到记事本。 在稍后的步骤中,需要将这两个值用于“appsettings.json”。

第三个组件是 Azure AI 搜索,可以在门户中创建它,也可以在 Azure 资源中找到现有的搜索服务

若要向搜索服务进行身份验证,需要服务 URL 和访问密钥。

  1. 登录到 Azure 门户,在搜索服务的“概览”页中获取 URL。 示例终结点可能类似于 https://mydemo.search.azure.cn

  2. 在“设置”>“密钥”中,获取有关该服务的完全权限的管理员密钥 。 有两个可交换的管理员密钥,为保证业务连续性而提供,以防需要滚动一个密钥。 可以在请求中使用主要或辅助密钥来添加、修改和删除对象。

    获取服务名称以及管理密钥和查询密钥

具有有效的密钥可以在发送请求的应用程序与处理请求的服务之间建立信任关系,这种信任关系以每个请求为基础。

2 - 设置环境

  1. 启动 Visual Studio,在“工具”菜单中,依次选择“NuGet 包管理器”、“管理解决方案...的 NuGet 包”。

  2. 在“浏览”选项卡中,找到并安装 Azure.Search.Documents(11.0 或更高版本) 。

  3. 搜索 Microsoft.Extensions.Configuration 和 Microsoft.Extensions.Configuration.Json NuGet 包并安装它们 。

  4. 打开解决方案文件 /v11/AzureSearchMultipleDataSources.sln。

  5. 在解决方案资源管理器中,编辑 appsettings.json 文件以添加连接信息。

    {
      "SearchServiceUri": "<YourSearchServiceURL>",
      "SearchServiceAdminApiKey": "<YourSearchServiceAdminApiKey>",
      "BlobStorageAccountName": "<YourBlobStorageAccountName>",
      "BlobStorageConnectionString": "<YourBlobStorageConnectionString>",
      "CosmosDBConnectionString": "<YourCosmosDBConnectionString>",
      "CosmosDBDatabaseName": "hotel-rooms-db"
    }
    

前两个条目是搜索服务的 URL 和管理密钥。 使用完整终结点,例如 https://mydemo.search.azure.cn

其余条目指定 Azure Blob 存储和 Azure Cosmos DB 数据源的帐户名称和连接字符串信息。

3 - 映射键字段

合并内容需要两个数据流针对搜索索引中的相同文档。

在 Azure AI 搜索中,键字段用于唯一标识每个文档。 每个搜索索引必须只有一个类型为 Edm.String 的键字段。 必须为添加到索引中的每个数据源文档设置键字段。 (事实上,它是唯一的必填字段。)

为多个数据源的数据编制索引时,请确保传入的每个行或文档包含一个通用文档键,用于将物理上不同的两个源文档的数据合并成组合索引中的新搜索文档。

通常需要进行一些前期规划,为索引确定有意义的文档键,并确保它存在于两个数据源中。 在本演示中,Azure Cosmos DB 中每家酒店的 HotelId 键也存在于 Blob 存储中的客房 JSON Blob 内。

在索引编制过程中,Azure AI 搜索索引器可以使用字段映射来重命名数据字段甚至重新设置其格式,以便可以将源数据定向到正确的索引字段。 例如,在 Azure Cosmos DB 中,酒店标识符称为 HotelId。 但在酒店房间的 JSON blob 文件中,酒店标识符则名为 Id。 程序通过将 Blob 中的 Id 字段映射到索引器中的 HotelId 密钥字段来处理此差异。

注意

大多数情况下,自动生成的文档键(如某些索引器默认创建的),不会为合并的索引提供有用的文档键。 一般而言,会需要使用数据源中已存在的或可轻松添加到数据源中的有意义且唯一的键值。

4 - 浏览代码

指定数据和配置设置后,/v11/AzureSearchMultipleDataSources.sln 中的示例程序应已可以生成并运行。

此简单的 C#/.NET 控制台应用程序执行以下任务:

  • 基于 C# Hotel 类的数据结构(该类还引用 Address 和 Room 类)创建新索引。
  • 创建新的数据源以及用于将 Azure Cosmos DB 数据映射到索引字段的索引器。 该数据源和索引器都是 Azure AI 搜索中的对象。
  • 运行该索引器以从 Azure Cosmos DB 加载酒店数据。
  • 创建另一个数据源以及用于将 JSON Blob 数据映射到索引字段的索引器。
  • 运行第二个索引器以从 Blob 存储加载客房数据。

运行该程序之前,请抽时间研究此示例的代码、索引和索引器定义。 相关代码在两个文件中:

  • Hotel.cs 包含用于定义索引的架构
  • Program.cs 包含用于创建 Azure AI 搜索索引、数据源和索引器,以及将合并的结果加载到索引中的函数。

创建索引

此示例程序使用 CreateIndexAsync 来定义和创建 Azure AI 搜索索引。 它利用 FieldBuilder 类,从 C# 数据模型类来生成索引结构。

数据模型由“酒店”类定义,该类还包含对“地址”和“房间”类的引用。 FieldBuilder 向下钻取多个类定义,从而为索引生成复杂的数据结构。 元数据标记用于定义每个字段的属性,例如字段是否可搜索或可排序。

程序在创建新索引器之前会先删除具有相同名称的任何现有索引,以应对你想要多次运行此示例的情况。

Hotel.cs 文件中的以下代码片段显示了单个字段,后跟对另一个数据模型类 Room[] 的引用,而该类是在 Room.cs 文件(未显示)中定义的。

. . .
[SimpleField(IsFilterable = true, IsKey = true)]
public string HotelId { get; set; }

[SearchableField(IsFilterable = true, IsSortable = true)]
public string HotelName { get; set; }
. . .
public Room[] Rooms { get; set; }
. . .

在 Program.cs 文件中,使用由 FieldBuilder.Build 方法生成的名称和字段集合定义了 SearchIndex,并通过以下方式创建了它:

private static async Task CreateIndexAsync(string indexName, SearchIndexClient indexClient)
{
    // Create a new search index structure that matches the properties of the Hotel class.
    // The Address and Room classes are referenced from the Hotel class. The FieldBuilder
    // will enumerate these to create a complex data structure for the index.
    FieldBuilder builder = new FieldBuilder();
    var definition = new SearchIndex(indexName, builder.Build(typeof(Hotel)));

    await indexClient.CreateIndexAsync(definition);
}

创建 Azure Cosmos DB 数据源和索引器

接下来,主程序包含用于为酒店数据创建 Azure Cosmos DB 数据源的逻辑。

首先,它将 Azure Cosmos DB 数据库名称连接到连接字符串。 然后,它定义一个 SearchIndexerDataSourceConnection 对象。

private static async Task CreateAndRunCosmosDbIndexerAsync(string indexName, SearchIndexerClient indexerClient)
{
    // Append the database name to the connection string
    string cosmosConnectString =
        configuration["CosmosDBConnectionString"]
        + ";Database="
        + configuration["CosmosDBDatabaseName"];

    SearchIndexerDataSourceConnection cosmosDbDataSource = new SearchIndexerDataSourceConnection(
        name: configuration["CosmosDBDatabaseName"],
        type: SearchIndexerDataSourceType.CosmosDb,
        connectionString: cosmosConnectString,
        container: new SearchIndexerDataContainer("hotels"));

    // The Azure Cosmos DB data source does not need to be deleted if it already exists,
    // but the connection string might need to be updated if it has changed.
    await indexerClient.CreateOrUpdateDataSourceConnectionAsync(cosmosDbDataSource);

创建数据源后,该程序会设置一个名为 hotel-rooms-cosmos-indexer 的 Azure Cosmos DB 索引器。

该程序将更新具有相同名称的所有现有索引器,并使用上述代码的内容覆盖现有索引器。 它还包含重置和运行操作,因此你可以多次运行此示例。

以下示例为索引器定义了一个计划,以使其每天运行一次。 如果不希望索引器在将来再次自动运行,可以从该调用中删除该计划属性。

SearchIndexer cosmosDbIndexer = new SearchIndexer(
    name: "hotel-rooms-cosmos-indexer",
    dataSourceName: cosmosDbDataSource.Name,
    targetIndexName: indexName)
{
    Schedule = new IndexingSchedule(TimeSpan.FromDays(1))
};

// Indexers keep metadata about how much they have already indexed.
// If we already ran the indexer, it "remembers" and does not run again.
// To avoid this, reset the indexer if it exists.
try
{
    await indexerClient.GetIndexerAsync(cosmosDbIndexer.Name);
    // Reset the indexer if it exists.
    await indexerClient.ResetIndexerAsync(cosmosDbIndexer.Name);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
    // If the indexer does not exist, 404 will be thrown.
}

await indexerClient.CreateOrUpdateIndexerAsync(cosmosDbIndexer);

Console.WriteLine("Running Azure Cosmos DB indexer...\n");

try
{
    // Run the indexer.
    await indexerClient.RunIndexerAsync(cosmosDbIndexer.Name);
}
catch (RequestFailedException ex) when (ex.Status == 429)
{
    Console.WriteLine("Failed to run indexer: {0}", ex.Message);
}

此示例包含一个简单的 try-catch 块来报告执行过程中可能发生的任何错误。

Azure Cosmos DB 索引器运行后,搜索索引将包含一套完整的示例酒店文档。 但是每家酒店的房间字段将是空数组,因为 Azure Cosmos DB 数据源省略了房间详细信息。 接下来,该程序将从 Blob 存储进行拉取,来加载和合并房间数据。

创建 Blob 存储数据源和索引器

为获取房间详细信息,该程序先设置一个 Blob 存储数据源,用于引用一组单独的 JSON blob 文件。

private static async Task CreateAndRunBlobIndexerAsync(string indexName, SearchIndexerClient indexerClient)
{
    SearchIndexerDataSourceConnection blobDataSource = new SearchIndexerDataSourceConnection(
        name: configuration["BlobStorageAccountName"],
        type: SearchIndexerDataSourceType.AzureBlob,
        connectionString: configuration["BlobStorageConnectionString"],
        container: new SearchIndexerDataContainer("hotel-rooms"));

    // The blob data source does not need to be deleted if it already exists,
    // but the connection string might need to be updated if it has changed.
    await indexerClient.CreateOrUpdateDataSourceConnectionAsync(blobDataSource);

创建数据源后,该程序会设置一个名为 hotel-rooms-blob-indexer 的 blob 索引器,如下所示。

JSON Blob 包含名为 Id 而不是 HotelId 的键字段。 该代码使用 FieldMapping 类来指示索引器将 Id 字段值定向到索引中的 HotelId 文档键。

Blob 存储索引器可以使用 IndexingParameters 来指定解析模式。 应根据 blob 表示单个文档还是同一 blob 中的多个文档来设置不同的解析模式。 在此示例中,每个 blob 表示单个 JSON 文档,因此代码使用 json 解析模式。 有关 JSON blob 的索引器分析参数的详细信息,请参阅为 JSON blob 编制索引

此示例为索引器定义了一个日程计划,以使其每天运行一次。 如果不希望索引器在将来再次自动运行,可以从该调用中删除该计划属性。

IndexingParameters parameters = new IndexingParameters();
parameters.Configuration.Add("parsingMode", "json");

SearchIndexer blobIndexer = new SearchIndexer(
    name: "hotel-rooms-blob-indexer",
    dataSourceName: blobDataSource.Name,
    targetIndexName: indexName)
{
    Parameters = parameters,
    Schedule = new IndexingSchedule(TimeSpan.FromDays(1))
};

// Map the Id field in the Room documents to the HotelId key field in the index
blobIndexer.FieldMappings.Add(new FieldMapping("Id") { TargetFieldName = "HotelId" });

// Reset the indexer if it already exists
try
{
    await indexerClient.GetIndexerAsync(blobIndexer.Name);
    await indexerClient.ResetIndexerAsync(blobIndexer.Name);
}
catch (RequestFailedException ex) when (ex.Status == 404) { }

await indexerClient.CreateOrUpdateIndexerAsync(blobIndexer);

try
{
    // Run the indexer.
    await searchService.Indexers.RunAsync(blobIndexer.Name);
}
catch (CloudException e) when (e.Response.StatusCode == (HttpStatusCode)429)
{
    Console.WriteLine("Failed to run indexer: {0}", e.Response.Content);
}

由于索引已使用 Azure Cosmos DB 数据库的酒店数据进行了填充,blob 索引器会更新索引中的现有文档,并添加房间的详细信息。

注意

如果有两个数据源中都有的非键字段且这些字段中的数据不匹配,则索引将包含最近一次运行的任何索引器的值。 在本示例中,这两个数据源都包含 HotelName 字段。 如果由于某种原因,对于具有相同键值的文档,此字段中的数据不同,则索引中存储的值将是最近被编入索引的数据源的 HotelName 数据。

可尝试使用门户中的搜索资源管理器 在程序开始运行后浏览填充的搜索索引。

在 Azure 门户中,打开搜索服务的“概述”页,在“索引”列表中找到“hotel-rooms-sample”索引 。

Azure AI 搜索索引列表

选择列表中的 hotel-rooms-sample 索引。 随即会显示索引的“搜索资源管理器”界面。 输入一个词(如“奢华”)进行查询。 得到的结果中至少会显示一个文档,此文档的房间数组中会显示一系列房间对象。

重置并重新运行

在开发的前期试验阶段,设计迭代的最实用方法是,删除 Azure AI 搜索中的对象,并允许代码重新生成它们。 资源名称是唯一的。 删除某个对象后,可以使用相同的名称重新创建它。

此示例代码将检查现有对象并将其删除或更新,使你能够重新运行程序。

也可以使用门户来删除索引、索引器和数据源。

清理资源

在自己的订阅中操作时,最好在项目结束时删除不再需要的资源。 持续运行资源可能会产生费用。 可以逐个删除资源,也可以删除资源组以删除整个资源集。

可以使用左侧导航窗格中的“所有资源”或“资源组”链接 ,在门户中查找和管理资源。

后续步骤

熟悉从多个源引入数据的概念后,接下来让我们从 Azure Cosmos DB 开始更详细地了解索引器配置。