你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

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

Azure AI 搜索支持将多个数据源中的数据导入、分析和索引到单个合并的搜索索引中。

本 C# 教程使用 Azure SDK for .NET 中的 Azure.Search.Documents 客户端库为 Azure Cosmos DB 实例中的示例酒店数据编制索引。 然后,将数据与从 Azure Blob 存储文档绘制的酒店房间详细信息合并。 结果是一个集成的酒店搜索索引,其中包含酒店文档,并将房间作为复杂的数据类型。

在本教程中,你将:

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

概述

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

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

Prerequisites

注意

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

创建服务

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

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

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

Azure Cosmos DB 入门

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

  2. 选择 数据资源管理器,然后选择“ 新建数据库”。

    新建数据库

  3. 输入名称 hotel-rooms-db。 接受其余设置的默认值。

    配置数据库

  4. 创建新容器。 使用之前创建的现有数据库。 输入 酒店 作为容器名称,并使用 /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 门户中创建它,也可以在 Azure 资源中找到现有的搜索服务

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

  1. 登录到 Azure 门户

  2. 在搜索服务的 “概述 ”页上,获取 URL。 示例终结点可能类似于 https://mydemo.search.windows.net

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

在发送请求和服务处理请求的应用程序之间,具有有效的密钥可建立每个请求的信任。

配置你的环境

  1. 打开 Visual Studio。

  2. “工具” 菜单中,选择 “NuGet 包管理器”,然后选择“ 管理解决方案的 NuGet 包...”

  3. “浏览 ”选项卡上,查找并安装 Azure.Search.Documents (版本 11.0 或更高版本)。

  4. 查找并安装 Microsoft.Extensions.ConfigurationMicrosoft.Extensions.Configuration.Json NuGet 包。

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

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

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

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

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

映射键字段

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

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

为来自多个数据源的数据编制索引时,请确保每个传入行或文档都包含一个通用文档键。 这样,就可以将数据从两个物理上不同的源文档合并到合并索引中的新搜索文档中。

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

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

注意

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

浏览代码

当数据和配置设置到位时, /v11/AzureSearchMultipleDataSources.sln 中的示例程序应准备好生成和运行。

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

  • 基于 C# Hotel 类的数据结构创建新索引,该类也引用地址和房间类。
  • 创建新的数据源以及用于将 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 文件中,使用由 方法生成的名称和字段集合定义了 SearchIndex,并通过以下方式创建了它FieldBuilder.Build

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 门户中的 搜索资源管理器 浏览填充的搜索索引。

在门户中,转到搜索服务的“概述”页,然后在“索引”列表中查找酒店房间示例索引。

Azure AI 搜索索引列表

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

重置并重新运行

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

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

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

清理资源

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

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

后续步骤

熟悉从多个源引入数据后,请仔细了解索引器配置,从 Azure Cosmos DB 开始: