Hi Joel,
This is what I'm trying to do:
_client.Search<UserDto>()
.Filter(
r =>
(r.FirstName.Prefix(filter.SearchLetter)) |
(r.MatchType(typeof(BasePage)) & ((BasePage)r).PageName.Prefix(filter.SearchLetter)))
The code above won't compile, because I can't cast a BasePage to a UserDto object. Is there another solution for this?
Thanks.
Hmm, that should compile although it may produce a warning about a conspicious cast. What exactly does the compiler say?
Given that you do get it to compile you probably want to do like below instead given that we're using &/| rather than &&/||:
(!r.MatchType(
typeof
(BasePage)) | ((BasePage)r).PageName.Prefix(filter.SearchLetter))
I'm getting two errors:
- Cannot convert lambda expression to type 'EPiServer.Find.Api.Querying.Filter' because it is not a delegate type
- Cannot convert type 'UserDto' to 'BasePage'
Regarding the "convert type" error you cannot cast your reference type in c# to another type if it isn't a basetype or a derived type, i.e there is no way for a UserDto object to be a BasePage.
To fix this you could either inherit BasePage from UserDto (or vice versa if it makes sense), or use:
client.search<BasePage>().
.Filter().
.Select(x => new UserDto { p1 = x.p1 ... })
to make the conversion yourself.
Regards,
Henrik
So it isn't possible to filter on two types if they haven't have the same basetype?
It is, but you need to either:
a) use unified search
or
b) find a way to get the compiler out of your way
In order to do b) you can instead search for Object and add filters that require objects to be either UserDto or BasePage and then apply the rest of your filters. Alternatively, I think you should be able to to ((BasePage)((Object)x)).PageName.Prefix... Nasty looking, yes :)
Great, thanks! I managed to get it working with the Object type. Now I've the following:
result = result.Filter(r => (r.MatchType(typeof (UserDto)) & ((UserDto) r).FirstName.Prefix(filter.SearchLetter)));
result = result.OrFilter(r => (r.MatchTypeHierarchy(typeof(BaseLocationPage)) & ((BaseLocationPage)r).PageName.Prefix(filter.SearchLetter)));
var query = result.Take(1000);
query.GetResult();
The only this is that if get the result, only the properties of the type BaseLocationPage are filled, the properties of the basetype PageData are all empty?
Hmm, could you paste the full code?
To simplify things (potentially A LOT), would it be an option to have both classes implement a common interface?
/// <summary>
/// Search user in index
/// </summary>
/// <returns></returns>
public IEnumerable<Object> SearchUserInIndex_1(SearchUsersFilter filter)
{
var list = new List<WhoWhatWhereSearchResult>();
var result = _client.Search<Object>();
if (!string.IsNullOrEmpty(filter.Criteria))
{
var searchResult = result.For(filter.Criteria);
if (filter.SearchCollegue || filter.SearchExpertise)
{
result.Filter(r => r.MatchTypeHierarchy(typeof(UserDto)));
}
if (filter.SearchLocations)
{
result.Filter(r => r.MatchTypeHierarchy(typeof (BaseLocationPage)));
}
result = searchResult;
}
if (filter.SortByFirstName)
result = result.OrderBy(u => (u is UserDto ? ((UserDto)u).FirstName : ((BaseLocationPage)u).PageName));
else if (filter.SortByLastname)
result = result.OrderBy(u => (u is UserDto ? ((UserDto)u).LastName : ((BaseLocationPage)u).PageName));
if (!string.IsNullOrEmpty(filter.SearchLetter))
{
result = result.Filter(r => (r.MatchType(typeof (UserDto)) & ((UserDto) r).FirstName.Prefix(filter.SearchLetter)));
result = result.OrFilter(r => (r.MatchTypeHierarchy(typeof(BaseLocationPage)) & ((BaseLocationPage)r).PageName.Prefix(filter.SearchLetter)));
}
var query = result.Take(1000);
int totalNumberOfUsers;
var batch = query.GetResult();
foreach (var page in batch.ToList())
{
list.Add(GetSearchResultItem(page));
}
totalNumberOfUsers = batch.TotalMatching;
var nextBatchFrom = 1000;
while (nextBatchFrom < totalNumberOfUsers)
{
batch = query.Skip(nextBatchFrom).GetResult();
foreach (var page in batch.ToList())
{
list.Add(GetSearchResultItem(page));
}
nextBatchFrom += 1000;
}
return list;
}
/// <summary>
/// Get search result item
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
private WhoWhatWhereSearchResult GetSearchResultItem(Object obj)
{
if (obj is UserDto)
return WhoWhatWhereSearchResult.FromUserDto((UserDto) obj);
else if (obj is BaseLocationPage)
return WhoWhatWhereSearchResult.FromLocation((BaseLocationPage)obj);
return new WhoWhatWhereSearchResult();
}
public class WhoWhatWhereSearchResult
{
public int Id { get; set; }
public string Name { get; set; }
public UserDto User { get; set; }
public BaseLocationPage Location { get; set; }
public static WhoWhatWhereSearchResult FromUserDto(UserDto user)
{
var item = new WhoWhatWhereSearchResult
{
Id = user.Id,
Name = user.DisplayName,
User = user
};
return item;
}
public static WhoWhatWhereSearchResult FromLocation(BaseLocationPage page)
{
var item = new WhoWhatWhereSearchResult
{
Id = page.PageLink.ID,
Name = page.PageName,
Location = page
};
return item;
}
}
Would you mind posting the code for the GetSearchResultItem method as well?
Thanks.
What's strange is that you're actually getting anything back instead of an exception. Typically you can't retrieve full PageData objects from the index as they are hard to deserialize. Instead we get them by ID (well, PageReference). Anyway, two (more) questions:
1. Is the data you use from WhoWhatWhereSearchResult.User and from WhoWhatWhereSearchResult.Location limited? What I'm getting at is; are you only using a limited number of properties from the UserDto and PageData objects or do you truly need the full objects?
2. Is there an API method somewhere in your application to get a UserDto by ID, and if so, is it fast/cached?
No I'm not using all of the data, only a few properties (3 of User and 1 of Location) that I need in my view.
Yes there is an API method where I can retrieve a UserDto by ID and it is being cached.
Cool, then I'd say you have two viable options to choose from:
A) Use the Select method to retrieve only UserId and PageLink.ID from the index and pass both to a method that retrieves the original objects from either your user API or from EPiServer's API. It would look something like this:
query.Select(x => GetResultObject(((UserDto)x).User.Id, ((PageData)x).PageLink.ID).GetResult();
...
private static Object GetResultObject(int? userId, int? pageId)
{
if(userId.HasValue)
{
return myHappyUserApi.GetById(userId.Value);
}
if(pageId.HasValue)
{
return DataFactory.Instance.Get<BaseLocationPage>(new PageReference(pageId.Value));
}
throw new Exception("Something is wrong, we need to look at what we're getting back");
}
B) Create an interface containing all the data that you need, both in terms of what you want to filter on and what you want to retrieve. Make both UserDto and BaseLocationPage implement the interface. Create a class that contains the data that you need to retrieve from the index. Search for your interface and then add a projection (using the Select method) where you project from the interface to your result class.
Personally I'd opt for B) but A) is probably quicker to implement.
The problem with solution A is that ((PageData)x).PageLink.ID
is empty for some reason, only the properties of the type BaseLocationPage are filled not of the basetype PageData? I think the best thing to do is to implement a common interface.
Thanks for your help!
Can you filter out pages who's parent page is not a certain pageType. Basically, in the main site search I dont want to show pages who's Parent is a Member PageType.
Thanks
Jon
See answer in the following post: http://world.episerver.com/forum/developer-forum/-EPiServer-75-CMS/Thread-Container/2015/8/filter-out-parent-pages-by-pagetype/
Hi,
How can I filter over multiple types. For example I've got two different Types: A and B, both with a string property with two different names. How can I filter on both types to select only the objects with the string property value starting with the letter C?
In the documentation it's clear how to search in two different types with the method IncludeType, but how can I use this method with specific type filters?
Thanks