The search is working, but I'd like to modify the query to sort according to a date property. Can someone point me on the right track?
var query = index.Searcher.CreateQuery(IndexTypes.Content);
var queryExecutor = query.ParentId(2210).And().NodeTypeAlias("newsArticle").OrderBy(x => x.Value("date"));
Compiler error: Cannot convert lambda expression to type 'SortableField' because it is not a delegate type
Should I not use a lambda expression here, or is there another way to do it?
My updated, albeit, still not working (but compiling) attempt...
var query = index.Searcher.CreateQuery(IndexTypes.Content);
var queryExecutor = query.ParentId(2210).And().NodeTypeAlias("newsArticle").OrderBy(new Examine.Search.SortableField("createDate", Examine.Search.SortType.Long));
I see a createDate field with long values within the Examine Management tool, but it still doesn't seem to be sorting correctly.
By default, Umbraco stores all values in examine as strings, so sorting by date is not quite as straight forward as you might think.
If you want to order by your date field, you should first update the index and add a custom field that stores the date as type 'long'.
Here's an example how you can do that:
public class IndexComposer
: ComponentComposer<IndexComponent>
{
public override void Compose(IUmbracoBuilder builder)
{
base.Compose(builder);
builder.Services.ConfigureOptions<ConfigureIndexOptions>();
}
}
// Configure index options and add a new field definition to the external index of type 'long'
public class ConfigureIndexOptions : IConfigureNamedOptions<LuceneDirectoryIndexOptions>
{
public void Configure(string name, LuceneDirectoryIndexOptions options)
{
if (name != Constants.UmbracoIndexes.ExternalIndexName) return;
options.FieldDefinitions.AddOrUpdate(new FieldDefinition(Defaults.Search.SortableUpdateDateField, FieldDefinitionTypes.Long));
}
public void Configure(LuceneDirectoryIndexOptions options)
=> Configure(string.Empty, options);
}
// Attach to index update event to update the newly created field with the date of the content
public class IndexComponent : IComponent
{
private readonly IExamineManager _examineManager;
private readonly IUmbracoContextFactory _umbracoContextFactory;
public IndexComponent(IExamineManager examineManager,
IUmbracoContextFactory umbracoContextFactory)
{
_examineManager = examineManager;
_umbracoContextFactory = umbracoContextFactory;
}
public void Initialize()
{
if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) return;
index.TransformingIndexValues += OnTransformingIndexValues;
}
private void OnTransformingIndexValues(object sender, IndexingItemEventArgs e)
{
if (e.ValueSet.Category != IndexTypes.Content) return;
using var cref = _umbracoContextFactory.EnsureUmbracoContext();
var content = cref.UmbracoContext.Content.GetById(int.Parse(e.ValueSet.Id));
if (content is null) return;
e.ValueSet.Set(Defaults.Search.SortableUpdateDateField, content.UpdateDate.Ticks);
}
public void Terminate()
{ }
}
If you add this code, you can then order by that custom field with your index.
I'm a bit of a n00b extending Umbraco 9, so let me know if I'm off base. I assume this code doesn't belong within my view, so I created these three classes within the root of my Umbraco 9 project, then chased down their assembly references.
Unfortunately I'm getting a compiler error: The name 'Defaults' does not exist in the current context
I should've clarified: the defaults class is a class that I made myself! It's my place to store "magic strings" and constants. You can replace it with any string of your choice, as long as you use the same string everywhere. If you would like some inspiration, here's my implementation of the Defaults class:
public static partial class Defaults
{
public static class Search
{
public const string SortableUpdateDateField = "sortUpdateDate";
}
}
You can imagine, for different topics, I have different constants, but they're all nicely bundled under the Defaults static partial class.
Also don't forget to update your actual search query to sort on this new sortable field and not on the original update date field!
I think I got this working with the native createDate property. However, I'm struggling to get this to work with a custom property. I have a date property that is using the native Date Picker (Umbraco.DateTime) component.
I updated the classes and view to reference this property name and it compiles, but is unsorted. Looking at the examine management, I see the property still in a DateTime format, not a long, which I assume is the issue.
Or do I need to go the other way and update the configure to use FieldDefinitionTypes.DateTime? Unsure of how OnTransformingIndexValues would need to be updated then.
yeah, you're supposed to see the datetime as one big number, so something is not quite right there.
There's a few things that you should pay some attention to:
Did you .AddOrUpdate the field definition with FieldDefinitionTypes.Long?
Did you .Set the DateTime.Ticks and not the datetime object itself? (This seems most likely to be the issue, looking at the screenshot that you sent)
In your search query, did you sort with SortableField, the correct field name and SortType.Long (Your code snippets seems to suggest you do, so that's al cool)
I'm not 100%, but I remember it was important to prepend the field name of sortable fields with "sort". So you named your field "date", but I think it only works if you name it "sortdate". I don't know for sure though, that's something you can experiment with.
It occurred to me that I needed to rebuild the external index. After doing that, I am now seeing long values for that field in the Examine Management tool!
Unfortunately, the view still isn't sorting correctly. It runs, but seems to ignore the OrderBy in my query entirely. When I republish a random news article, it consistently moves to the bottom of the list.
I also tried renaming the property to "sortdate" as you suggested, then rebuilt the index. Same behavior unfortunately.
View Snippet:
...
var query = index.Searcher.CreateQuery(IndexTypes.Content);
var queryExecutor = query.ParentId(2210).And().NodeTypeAlias("newsArticle").OrderBy(new Examine.Search.SortableField("sortdate", Examine.Search.SortType.Long));
foreach (var item in _publishedContentQuery.Search(queryExecutor))
{
...
}
Classes:
public class IndexComposer
: ComponentComposer<IndexComponent>
{
public override void Compose(IUmbracoBuilder builder)
{
base.Compose(builder);
builder.Services.ConfigureOptions<ConfigureIndexOptions>();
}
}
public class ConfigureIndexOptions : IConfigureNamedOptions<LuceneDirectoryIndexOptions>
{
public void Configure(string name, LuceneDirectoryIndexOptions options)
{
if (name != Constants.UmbracoIndexes.ExternalIndexName) return;
options.FieldDefinitions.AddOrUpdate(new FieldDefinition("sortdate", FieldDefinitionTypes.Long));
}
public void Configure(LuceneDirectoryIndexOptions options)
=> Configure(string.Empty, options);
}
public class IndexComponent : IComponent
{
private readonly IExamineManager _examineManager;
private readonly IUmbracoContextFactory _umbracoContextFactory;
public IndexComponent(IExamineManager examineManager,
IUmbracoContextFactory umbracoContextFactory)
{
_examineManager = examineManager;
_umbracoContextFactory = umbracoContextFactory;
}
public void Initialize()
{
if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) return;
index.TransformingIndexValues += OnTransformingIndexValues;
}
private void OnTransformingIndexValues(object sender, IndexingItemEventArgs e)
{
if (e.ValueSet.Category != IndexTypes.Content) return;
using var cref = _umbracoContextFactory.EnsureUmbracoContext();
var content = cref.UmbracoContext.Content.GetById(int.Parse(e.ValueSet.Id));
if (content is null) return;
e.ValueSet.Set("sortdate", content.UpdateDate.Ticks);
}
public void Terminate()
{ }
}
Sounds to me like it's working. You probably expect the page with the latest update to be on top though. Might I suggest using .OrderByDescending(...), rather than .OrderBy(...)? You see, OrderBy orders in ascending order. The most recent dates have the highest number of ticks and will therefore be moved to the bottom!
I also discovered that I needed to republish all the nodes (which is easy in Umbraco 9 from the parent node). Of course I ran into a runtime error because some nodes didn't have the sortdate property. This was resolved with a simple HasValue check.
Either way, THANK YOU sooo much. I would have never been able to figure this without your help!
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
namespace <your namespace>
{
public class IndexComposer
: ComponentComposer<IndexComponent>
{
public override void Compose(IUmbracoBuilder builder)
{
base.Compose(builder);
builder.Services.ConfigureOptions<ConfigureIndexOptions>();
}
}
}
ConfigureIndexOptions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Extensions;
using Examine;
using Examine.Lucene;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
namespace <your namespace>
{
public class ConfigureIndexOptions : IConfigureNamedOptions<LuceneDirectoryIndexOptions>
{
public void Configure(string name, LuceneDirectoryIndexOptions options)
{
if (name != Constants.UmbracoIndexes.ExternalIndexName) return;
options.FieldDefinitions.AddOrUpdate(new FieldDefinition("sortdate", FieldDefinitionTypes.Long));
}
public void Configure(LuceneDirectoryIndexOptions options)
=> Configure(string.Empty, options);
}
}
IndexComponent.cs
using System;
using Umbraco.Cms.Core.Composing;
using Umbraco.Extensions;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Web;
using Examine;
using Umbraco.Cms.Infrastructure.Examine;
namespace <your namespace>
{
public class IndexComponent : IComponent
{
private readonly IExamineManager _examineManager;
private readonly IUmbracoContextFactory _umbracoContextFactory;
public IndexComponent(IExamineManager examineManager,
IUmbracoContextFactory umbracoContextFactory)
{
_examineManager = examineManager;
_umbracoContextFactory = umbracoContextFactory;
}
public void Initialize()
{
if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) return;
index.TransformingIndexValues += OnTransformingIndexValues;
}
private void OnTransformingIndexValues(object sender, IndexingItemEventArgs e)
{
if (e.ValueSet.Category != IndexTypes.Content) return;
using var cref = _umbracoContextFactory.EnsureUmbracoContext();
var content = cref.UmbracoContext.Content.GetById(int.Parse(e.ValueSet.Id));
if (content is null) return;
if (!content.HasValue("sortdate")) return;
e.ValueSet.Set("sortdate", DateTime.Parse(content.Value("sortdate").ToString()).Ticks);
}
public void Terminate()
{ }
}
}
Remember to rebuild the external index and republish your nodes.
Thanks for this post, was very useful.
By the way, for me re-publishing nodes weren't necessary just hitting re-index makes them. As I needed sorting for special nodes I made some changes to OnTransformingIndexValues. I put it here may help someone!
private void OnTransformingIndexValues(object sender, IndexingItemEventArgs e)
{
if (e.ValueSet.Category != IndexTypes.Content)
return;
using var cref = _umbracoContextFactory.EnsureUmbracoContext();
var content = cref.UmbracoContext.Content.GetById(int.Parse(e.ValueSet.Id));
if (content is IXXXComposition item)
{
bool isSomething= item.IsSomething();
var updatedValues =
e.ValueSet.Values.ToDictionary(x => x.Key, x => x.Value.ToList());
updatedValues["IsSomething"] = new List<object> { isSomething};
updatedValues["SortDate"] =
new List<object>
{
DateTime.Parse(item.Value(nameof(IXXXComposition .PublishedDate)).ToString())
.Ticks
};
e.SetValues(
updatedValues.ToDictionary(x => x.Key, x => (IEnumerable<object>)x.Value));
}
}
ps: PublishedDate is the date picker value for my docType (IXXXComposition) which I needed to sort on that.
Examine sort in Umbraco 9
I am following documentation posted here, specifically the IQueryExecutor queryExecutor search: https://our.umbraco.com/documentation/reference/querying/IPublishedContentQuery/
The search is working, but I'd like to modify the query to sort according to a date property. Can someone point me on the right track?
Compiler error: Cannot convert lambda expression to type 'SortableField' because it is not a delegate type
Should I not use a lambda expression here, or is there another way to do it?
Thanks in advance!
My updated, albeit, still not working (but compiling) attempt...
I see a createDate field with long values within the Examine Management tool, but it still doesn't seem to be sorting correctly.
Hi Mike,
By default, Umbraco stores all values in examine as strings, so sorting by date is not quite as straight forward as you might think.
If you want to order by your date field, you should first update the index and add a custom field that stores the date as type 'long'.
Here's an example how you can do that:
If you add this code, you can then order by that custom field with your index.
Hi Dennis, thanks for your response!
I'm a bit of a n00b extending Umbraco 9, so let me know if I'm off base. I assume this code doesn't belong within my view, so I created these three classes within the root of my Umbraco 9 project, then chased down their assembly references.
Unfortunately I'm getting a compiler error: The name 'Defaults' does not exist in the current context
Any pointers where to go from here?
Hi Mike,
You are on the right track!
I should've clarified: the defaults class is a class that I made myself! It's my place to store "magic strings" and constants. You can replace it with any string of your choice, as long as you use the same string everywhere. If you would like some inspiration, here's my implementation of the
Defaults
class:You can imagine, for different topics, I have different constants, but they're all nicely bundled under the Defaults static partial class.
Also don't forget to update your actual search query to sort on this new sortable field and not on the original update date field!
Much appreciated, Dennis!
I think I got this working with the native createDate property. However, I'm struggling to get this to work with a custom property. I have a date property that is using the native Date Picker (Umbraco.DateTime) component.
I updated the classes and view to reference this property name and it compiles, but is unsorted. Looking at the examine management, I see the property still in a DateTime format, not a long, which I assume is the issue.
Or do I need to go the other way and update the configure to use FieldDefinitionTypes.DateTime? Unsure of how OnTransformingIndexValues would need to be updated then.
Again, many thanks for your help!
Hi Mike,
yeah, you're supposed to see the datetime as one big number, so something is not quite right there.
There's a few things that you should pay some attention to:
FieldDefinitionTypes.Long
?SortType.Long
(Your code snippets seems to suggest you do, so that's al cool)Hi Dennis,
Thanks again for your response.
It occurred to me that I needed to rebuild the external index. After doing that, I am now seeing long values for that field in the Examine Management tool!
Unfortunately, the view still isn't sorting correctly. It runs, but seems to ignore the OrderBy in my query entirely. When I republish a random news article, it consistently moves to the bottom of the list.
I also tried renaming the property to "sortdate" as you suggested, then rebuilt the index. Same behavior unfortunately.
View Snippet:
Classes:
Any further suggestions?
Hi Mike,
Sounds to me like it's working. You probably expect the page with the latest update to be on top though. Might I suggest using
.OrderByDescending(...)
, rather than.OrderBy(...)
? You see, OrderBy orders in ascending order. The most recent dates have the highest number of ticks and will therefore be moved to the bottom!Hi Dennis,
Got it working!
Yes, a descending sort makes more sense.
OnTransformingIndexValues Ticks was using UpdateDate, not sortdate. So I adjusted that accordingly:
I also discovered that I needed to republish all the nodes (which is easy in Umbraco 9 from the parent node). Of course I ran into a runtime error because some nodes didn't have the sortdate property. This was resolved with a simple HasValue check.
Either way, THANK YOU sooo much. I would have never been able to figure this without your help!
Final code if anyone stumbles upon this.
View:
IndexComposer.cs
ConfigureIndexOptions.cs
IndexComponent.cs
Remember to rebuild the external index and republish your nodes.
Thanks for this post, was very useful. By the way, for me re-publishing nodes weren't necessary just hitting re-index makes them. As I needed sorting for special nodes I made some changes to OnTransformingIndexValues. I put it here may help someone!
ps: PublishedDate is the date picker value for my docType (IXXXComposition) which I needed to sort on that.
is working on a reply...