Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reflection-based Search index definition #2536

Merged
merged 1 commit into from
Nov 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.

namespace Microsoft.Azure.Search
{
using System;
using Microsoft.Azure.Search.Models;

/// <summary>
/// Indicates that the <see cref="Field"/> generated by <see cref="FieldBuilder"/> for
/// the target property should have its <see cref="Field.Analyzer"/> property set to the
/// specified analyzer.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class AnalyzerAttribute : Attribute
{
/// <summary>
/// Indicates that the specified analyzer should be used.
/// </summary>
/// <param name="analyzerName">
/// The name of the analyzer. Use one of the names in <see cref="AnalyzerName.AsString"/>
/// or the name of a custom analyzer.
/// </param>
public AnalyzerAttribute(string analyzerName)
{
Name = analyzerName;
}

public string Name { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.

namespace Microsoft.Azure.Search
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Azure.Search.Models;
using Microsoft.Spatial;
using Newtonsoft.Json.Serialization;

public static class FieldBuilder
{
/// <summary>
/// Creates a collection of <see cref="Field"/> objects corresponding to
/// the properties of the type supplied.
/// </summary>
/// <typeparam name="T">
/// The type for which fields will be created, based on its properties.
/// </typeparam>
/// <returns>A collection of fields.</returns>
public static IList<Field> BuildForType<T>()
{
bool useCamelCase = SerializePropertyNamesAsCamelCaseAttribute.IsDefinedOnType<T>();
IContractResolver resolver = useCamelCase
? JsonUtility.CamelCaseResolver
: JsonUtility.DefaultResolver;
return BuildForType<T>(resolver);
}

/// <summary>
/// Creates a collection of <see cref="Field"/> objects corresponding to
/// the properties of the type supplied.
/// </summary>
/// <typeparam name="T">
/// The type for which fields will be created, based on its properties.
/// </typeparam>
/// <param name="contractResolver">
/// Contract resolver that the <see cref="SearchIndexClient"/> will use.
/// This ensures that the field names are generated in a way that is
/// consistent with the way the model will be serialized.
/// </param>
/// <returns>A collection of fields.</returns>
public static IList<Field> BuildForType<T>(IContractResolver contractResolver)
{
var contract = (JsonObjectContract) contractResolver.ResolveContract(typeof(T));
var fields = new List<Field>();
foreach (JsonProperty prop in contract.Properties)
{
DataType dataType = GetDataType(prop.PropertyType, prop.PropertyName);

var field = new Field(prop.PropertyName, dataType);

IList<Attribute> attributes = prop.AttributeProvider.GetAttributes(true);
foreach (Attribute attribute in attributes)
{
IsRetrievableAttribute isRetrievableAttribute;
AnalyzerAttribute analyzerAttribute;
SearchAnalyzerAttribute searchAnalyzerAttribute;
IndexAnalyzerAttribute indexAnalyzerAttribute;
if (attribute is IsSearchableAttribute)
{
field.IsSearchable = true;
}
else if (attribute is IsFilterableAttribute)
{
field.IsFilterable = true;
}
else if (attribute is IsSortableAttribute)
{
field.IsSortable = true;
}
else if (attribute is IsFacetableAttribute)
{
field.IsFacetable = true;
}
else if ((isRetrievableAttribute = attribute as IsRetrievableAttribute) != null)
{
field.IsRetrievable = isRetrievableAttribute.IsRetrievable;
}
else if ((analyzerAttribute = attribute as AnalyzerAttribute) != null)
{
field.Analyzer = AnalyzerName.Create(analyzerAttribute.Name);
}
else if ((searchAnalyzerAttribute = attribute as SearchAnalyzerAttribute) != null)
{
field.SearchAnalyzer = AnalyzerName.Create(searchAnalyzerAttribute.Name);
}
else if ((indexAnalyzerAttribute = attribute as IndexAnalyzerAttribute) != null)
{
field.IndexAnalyzer = AnalyzerName.Create(indexAnalyzerAttribute.Name);
}
else
{
// Match on name to avoid dependency - don't want to force people not using
// this feature to bring in the annotations component.
Type attributeType = attribute.GetType();
if (attributeType.FullName == "System.ComponentModel.DataAnnotations.KeyAttribute")
{
field.IsKey = true;
}
}
}

fields.Add(field);
}

return fields;
}

private static DataType GetDataType(Type propertyType, string propertyName)
{
if (propertyType == typeof(string))
{
return DataType.String;
}
if (propertyType.IsConstructedGenericType &&
propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return GetDataType(propertyType.GenericTypeArguments[0], propertyName);
}
if (propertyType == typeof(int))
{
return DataType.Int32;
}
if (propertyType == typeof(long))
{
return DataType.Int64;
}
if (propertyType == typeof(double))
{
return DataType.Double;
}
if (propertyType == typeof(bool))
{
return DataType.Boolean;
}
if (propertyType == typeof(DateTimeOffset) ||
propertyType == typeof(DateTime))
{
return DataType.DateTimeOffset;
}
if (propertyType == typeof(GeographyPoint))
{
return DataType.GeographyPoint;
}
Type elementType = GetElementTypeIfIEnumerable(propertyType);
if (elementType != null)
{
return DataType.Collection(GetDataType(elementType, propertyName));
}
TypeInfo ti = propertyType.GetTypeInfo();
var listElementTypes = ti
.ImplementedInterfaces
.Select(GetElementTypeIfIEnumerable)
.Where(p => p != null)
.ToList();
if (listElementTypes.Count == 1)
{
return DataType.Collection(GetDataType(listElementTypes[0], propertyName));
}

throw new ArgumentException(
$"Property {propertyName} has unsupported type {propertyType}",
nameof(propertyType));
}

private static Type GetElementTypeIfIEnumerable(Type t) =>
t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)
? t.GenericTypeArguments[0]
: null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.

namespace Microsoft.Azure.Search
{
using System;
using Microsoft.Azure.Search.Models;

/// <summary>
/// Indicates that the <see cref="Field"/> generated by <see cref="FieldBuilder"/> for
/// the target property should have its <see cref="Field.IndexAnalyzer"/> property set to the
/// specified analyzer.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class IndexAnalyzerAttribute : Attribute
{
/// <summary>
/// Indicates that the specified analyzer should be used.
/// </summary>
/// <param name="analyzerName">
/// The name of the analyzer. Use one of the names in <see cref="AnalyzerName.AsString"/>
/// or the name of a custom analyzer.
/// </param>
public IndexAnalyzerAttribute(string analyzerName)
{
Name = analyzerName;
}

public string Name { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.

namespace Microsoft.Azure.Search
{
using System;
using Microsoft.Azure.Search.Models;

/// <summary>
/// Indicates that it is possible to facet on this field. Not valid for
/// geo-point fields.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class IsFacetableAttribute : Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.

namespace Microsoft.Azure.Search
{
using System;
using Microsoft.Azure.Search.Models;

/// <summary>
/// Indicates that the field can be used in filter expressions.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class IsFilterableAttribute : Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.

namespace Microsoft.Azure.Search
{
using System;
using Microsoft.Azure.Search.Models;

/// <summary>
/// Indicates whether the field can be returned in a search result. This
/// defaults to true, so this attribute only has any effect if you use it
/// as [IsRetrievable(false)].
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class IsRetrievableAttribute : Attribute
{
public IsRetrievableAttribute(bool isRetrievable)
{
IsRetrievable = isRetrievable;
}

public bool IsRetrievable { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.

namespace Microsoft.Azure.Search
{
using System;
using Microsoft.Azure.Search.Models;

/// <summary>
/// Causes the field to be included in full-text searches. Valid only for
/// string or string collection fields.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class IsSearchableAttribute : Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.

namespace Microsoft.Azure.Search
{
using System;

using Microsoft.Azure.Search.Models;

/// <summary>
/// Indicates that the field can be used in orderby expressions. Not valid
/// for string collection fields.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class IsSortableAttribute : Attribute
{
}
}
Loading