/Was.OrcSearch/Metadata/AttributeMetadataCollection.cs |
@@ -1,6 +1,10 @@ |
using System; |
using System.Collections; |
using System.Collections.Generic; |
using System.ComponentModel; |
using System.Linq; |
using System.Reflection; |
using System.Threading; |
using Was.OrcSearch.Attributes; |
using Was.OrcSearch.Models; |
using Was.OrcSearch.Models.Interfaces; |
@@ -9,7 +13,7 @@ |
{ |
public class AttributeMetadataCollection : ReflectionMetadataCollection |
{ |
private static readonly Dictionary<Type, List<SearchableMetadata>> _propertiesCache = |
private static readonly Dictionary<Type, List<SearchableMetadata>> PropertiesCache = |
new Dictionary<Type, List<SearchableMetadata>>(); |
|
private readonly Type _targetType; |
@@ -24,30 +28,26 @@ |
{ |
get |
{ |
if (_propertiesCache.TryGetValue(_targetType, out var storedProperty)) |
if (PropertiesCache.TryGetValue(_targetType, out var storedProperty)) |
return storedProperty; |
|
var searchableProperties = new List<SearchableMetadata>(); |
|
var properties = _targetType.GetProperties(); |
foreach (var property in properties) |
{ |
var searchablePropertyAttribute = |
property.GetCustomAttribute(typeof(SearchablePropertyAttribute), false) as |
SearchablePropertyAttribute; |
if (searchablePropertyAttribute != null) |
{ |
var searchableProperty = new SearchableMetadata(property); |
if (!string.IsNullOrWhiteSpace(searchablePropertyAttribute.SearchName)) |
searchableProperty.SearchName = searchablePropertyAttribute.SearchName; |
if (!(property.GetCustomAttribute(typeof(SearchablePropertyAttribute), false) is |
SearchablePropertyAttribute searchablePropertyAttribute)) continue; |
|
var searchableProperty = new SearchableMetadata(property); |
if (!string.IsNullOrWhiteSpace(searchablePropertyAttribute.SearchName)) |
searchableProperty.SearchName = searchablePropertyAttribute.SearchName; |
|
searchableProperty.Analyze = searchablePropertyAttribute.Analyze; |
searchableProperty.Analyze = searchablePropertyAttribute.Analyze; |
|
searchableProperties.Add(searchableProperty); |
} |
searchableProperties.Add(searchableProperty); |
} |
|
_propertiesCache.Add(_targetType, searchableProperties); |
PropertiesCache.Add(_targetType, searchableProperties); |
return searchableProperties; |
} |
} |
/Was.OrcSearch/Services/Extensions/SearchServiceExtensions.cs |
@@ -0,0 +1,40 @@ |
using System; |
using System.Collections; |
using System.Collections.Generic; |
|
namespace Was.OrcSearch.Services.Extensions |
{ |
public static class SearchServiceExtensions |
{ |
/// <summary> |
/// Convert an object to an enumeration of strings. |
/// </summary> |
/// <param name="instance">the object to convert</param> |
/// <returns>a series of strings</returns> |
public static IEnumerable<string> Stringify(this object instance) |
{ |
if (instance == null) |
yield break; |
|
// Support primitive types and strings. |
var instanceType = instance.GetType(); |
if (instanceType.IsPrimitive|| instance is string) |
{ |
yield return instance.ToString(); |
yield break; |
} |
|
// Support for arrays. |
if (instance is IList list) |
{ |
foreach (var element in list) |
{ |
foreach (var item in Stringify(element)) |
{ |
yield return item; |
} |
} |
} |
} |
} |
} |
/Was.OrcSearch/Services/SearchQueryService.cs |
@@ -1,5 +1,7 @@ |
using System.Collections.Generic; |
using Lucene.Net.Analysis.Standard; |
using System; |
using System.Collections.Generic; |
using System.Linq; |
using Lucene.Net.Analysis; |
using Lucene.Net.Index; |
using Lucene.Net.QueryParsers; |
using Lucene.Net.Search; |
@@ -7,31 +9,63 @@ |
using Was.OrcSearch.Metadata; |
using Was.OrcSearch.Metadata.Interfaces; |
using Was.OrcSearch.Services.Interfaces; |
using Version = Lucene.Net.Util.Version; |
|
namespace Was.OrcSearch.Services |
{ |
public class SearchQueryService : ISearchQueryService |
public class SearchQueryService : ISearchQueryService, IDisposable |
{ |
public string GetSearchQuery(string filter, IEnumerable<ISearchableMetadata> searchableMetadatas) |
private readonly Analyzer _analyzer; |
private readonly Version _version = LuceneDefaults.Version; |
|
/// <summary> |
/// Default constructor using a default whitespace analyzer. |
/// </summary> |
public SearchQueryService() |
{ |
if (!filter.Contains(":")) |
{ |
filter = filter.PrepareOrcSearchFilter(); |
_analyzer = new WhitespaceAnalyzer(); |
} |
|
using (var analyzer = new StandardAnalyzer(LuceneDefaults.Version)) |
{ |
var fields = new List<string>(); |
foreach (var searchableMetadata in searchableMetadatas) fields.Add(searchableMetadata.SearchName); |
/// <summary> |
/// Instantiate an analyzer using a specific Lucene version. |
/// </summary> |
/// <param name="analyzer">The analyzer to use.</param> |
/// <param name="version">The lucene version.</param> |
public SearchQueryService(Analyzer analyzer, Version version) |
{ |
_version = version; |
} |
|
var parser = new MultiFieldQueryParser(LuceneDefaults.Version, fields.ToArray(), analyzer); |
var query = parser.Parse(filter); |
filter = query.ToString(); |
} |
} |
/// <summary> |
/// Instantiate a per-field analyzer. |
/// </summary> |
/// <param name="default">The default analyzer to use.</param> |
/// <param name="fieldAnalyzers">A collection of fields to analyzers.</param> |
public SearchQueryService(Analyzer @default, Dictionary<string, Analyzer> fieldAnalyzers) |
{ |
_analyzer = new PerFieldAnalyzerWrapper(@default, fieldAnalyzers); |
} |
|
return filter; |
public void Dispose() |
{ |
_analyzer?.Dispose(); |
} |
|
public string GetSearchQuery(string filter, IEnumerable<ISearchableMetadata> searchableMetadatas) |
{ |
if (filter.Contains(":")) |
return filter; |
|
filter = filter.PrepareOrcSearchFilter(); |
|
return new MultiFieldQueryParser( |
_version, |
searchableMetadatas |
.Select(searchableMetadata => searchableMetadata.SearchName).ToArray(), |
_analyzer) |
.Parse(filter).ToString(); |
} |
|
public string GetSearchQuery(params ISearchableMetadataValue[] searchableMetadataValues) |
{ |
var query = new PhraseQuery(); |
/Was.OrcSearch/Services/SearchServiceBase.cs |
@@ -10,6 +10,7 @@ |
using Was.OrcSearch.EventArgs; |
using Was.OrcSearch.Extensions; |
using Was.OrcSearch.Metadata.Interfaces; |
using Was.OrcSearch.Services.Extensions; |
using Was.OrcSearch.Services.Interfaces; |
|
namespace Was.OrcSearch.Services |
@@ -117,10 +118,15 @@ |
foreach (var searchableMetadata in searchableMetadatas) |
{ |
var searchableMetadataValue = searchableMetadata.GetValue(searchable.Instance); |
// TODO implement object to string helper. |
|
// DEBUG |
//Console.WriteLine("Stringifying: " + searchableMetadataValue); |
|
var searchableMetadataValueAsString = |
searchableMetadataValue |
.ToString(); //ObjectToStringHelper.ToString(searchableMetadataValue); |
string.Join(" ", searchableMetadataValue.Stringify()); |
|
// DEBUG |
//Console.WriteLine("String metadata: " + string.Join(" ", searchableMetadataValue.Stringify())); |
|
var field = new Field(searchableMetadata.SearchName, searchableMetadataValueAsString, |
Field.Store.YES, |
@@ -160,8 +166,7 @@ |
{ |
foreach (var searchable in searchables) |
{ |
int index; |
if (!_searchableIndexes.TryGetValue(searchable, out index)) continue; |
if (!_searchableIndexes.TryGetValue(searchable, out var index)) continue; |
|
var queryAsText = $"{IndexId}:{index}"; |
var parser = new QueryParser(LuceneDefaults.Version, string.Empty, analyzer); |
@@ -273,15 +278,15 @@ |
} |
} |
} |
catch (ParseException ex) |
catch (ParseException) |
{ |
//Log.Warning(ex, "Failed to parse search pattern"); |
throw ex; |
throw; |
} |
catch (Exception ex) |
catch (Exception) |
{ |
//Log.Error(ex, "An error occurred while searching, returning default results"); |
throw ex; |
throw; |
} |
finally |
{ |
/Was.OrcSearch/Was.OrcSearch.csproj |
@@ -35,11 +35,11 @@ |
<RootNamespace>Was.OrcSearch</RootNamespace> |
</PropertyGroup> |
<ItemGroup> |
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL"> |
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath> |
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73"> |
<HintPath>..\..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath> |
</Reference> |
<Reference Include="Lucene.Net, Version=3.0.3.0, Culture=neutral, PublicKeyToken=85089178b9ac3181, processorArchitecture=MSIL"> |
<HintPath>..\packages\Lucene.Net.3.0.3\lib\NET40\Lucene.Net.dll</HintPath> |
<Reference Include="Lucene.Net, Version=3.0.3.0, Culture=neutral, PublicKeyToken=85089178b9ac3181"> |
<HintPath>..\..\packages\Lucene.Net.3.0.3\lib\NET40\Lucene.Net.dll</HintPath> |
</Reference> |
<Reference Include="System" /> |
<Reference Include="System.Core" /> |
@@ -77,7 +77,7 @@ |
<Compile Include="Models\ReflectionObjectWithMetadata.cs" /> |
<Compile Include="Properties\AssemblyInfo.cs" /> |
<Compile Include="Services\DummySearchNavigationService.cs" /> |
<Compile Include="Services\Extensions\ISearchServiceExtensions.cs" /> |
<Compile Include="Services\Extensions\SearchServiceExtensions.cs" /> |
<Compile Include="Services\InMemorySearchService.cs" /> |
<Compile Include="Services\Interfaces\ISearchNavigationService.cs" /> |
<Compile Include="Services\Interfaces\ISearchQueryService.cs" /> |