I've been currently working with Azure Search, and I stumbled upon a scenario where I have two tables: one describes a place, and the other has multiple photos for each place.
I wanted to use two indexers to populate the same index, where one would populate the place's details (name, coordinates, etc), while the other would get the photos, run a custom WebApiSkill
that gets certain details from the photo, and add them to a complex collection on the index.
The issue is, I can't figure out how to populate that photo collection on the index. I have tried defining my outputFieldMappings
as Photos/*/Details
, or just Photos/Details
, but I keep getting the same error:
Microsoft.Rest.Azure.CloudException: 'Output field mapping specifies target field 'Photos/*/PhotoURI' that doesn't exist in the index'
Considering this, I would like to know if it is at all possible to populate a complex collection like this in Azure Search, and if it is, how would I go about doing it?
Tables:
CREATE TABLE Places
(
Id bigint not null IDENTITY(1,1) PRIMARY KEY,
Name nvarchar(50) not null,
Location nvarchar(50) not null
);
CREATE TABLE PlacePhotos
(
PhotoURI nvarchar(200) not null PRIMARY KEY,
PlaceId bigint not null REFERENCES Places (Id)
);
Classes used for creating the index:
public partial class Photo
{
[IsSearchable]
public string PhotoURI { get; set; }
public List<Details> Details { get; set; }
}
public partial class Place
{
[System.ComponentModel.DataAnnotations.Key]
public string Id { get; set; }
[IsSearchable, IsFilterable, IsSortable, IsFacetable]
public string Name { get; set; }
[IsSearchable, IsFilterable]
public string Location { get; set; }
[IsSearchable, IsFilterable, IsSortable]
public List<Photo> Photos { get; set; }
}
Indexer (with the PlacePhotos
table as its data source):
private static void Create_PlacePhotos_Indexer(SearchServiceClient serviceClient)
{
var indexerDef = new Indexer(
name: placePhotosIndexer,
dataSourceName: placePhotosDataSource,
targetIndexName: index,
skillsetName: skillset,
schedule: new IndexingSchedule
{
Interval = new TimeSpan(0, 30, 0)
},
fieldMappings: new List<FieldMapping>
{
new FieldMapping("PlaceId", "Id"),
new FieldMapping("PhotoURI", "PhotoURI")
},
outputFieldMappings: new List<FieldMapping>
{
new FieldMapping("/document/Id", "Id"),
new FieldMapping("/document/PhotoURI", "Photos/*/PhotoURI"),
new FieldMapping("/document/Details", "Photos/*/Details")
}
);
serviceClient.Indexers.CreateOrUpdate(indexerDef);
}
Output field mapping target field names must be top-level index field names, so Photos/*/PhotoURI
is not valid and you have to target Photos
directly.
To "shape" the Photos
value to be mapped, you can use the ShaperSkill
{
"@odata.type": "#Microsoft.Skills.Util.ShaperSkill",
"context": "/document/photos/*",
"inputs": [
{
"name": "PhotoURI",
"source": "/document/photos/*/PhotoURI"
},
{
"name": "Details",
"source": "/document/photos/*/Details"
}
],
"outputs": [
{
"name": "output",
"targetName": "shapedValue"
}
]
}
and output field mapping
new FieldMapping("/document/photos/*/shapedValue", "Photos")
NOTE - you might have to rearrange your skills that output URI and details, so they share a common context
. From your snippet above it looks like each document
has only 1 photo, which is different from your index definition.