Ravendb Search with OrderBy not working - ravendb

Im using the latest build of RavenDB (3.0.3800)
When I run a simple query with a Search and Orderby the Search is ignored. If I remove the OrderBy the Search works and returns the correct results
var query = _session.Query<Index_All.ReduceResult, Index_All>()
.Customize(x => x.WaitForNonStaleResults())
.Search(x => x.SearchTerm, "Some String")
.OrderBy(x => x.PublishDate);
This just returns all results, ignoring my Search completely.
Here is my Index:
public class Index_All : AbstractIndexCreationTask<MyDocuemnt,Index_All.ReduceResult>
{
// query model
public class ReduceResult
{
public string SearchTerm { get; set; }
public DateTimeOffset PublishDate { get; set; }
}
public Index_All()
{
Map = documents => from d in documents
let customer = LoadDocument<Customer>(d.Customer.Id)
let owner = LoadDocument<Customer>(d.Owner.Id)
select new
{
SearchQuery = new object[]
{
customer.Name,
owner.Name,
},
d.PublishDate,
};
Index(x => x.SearchTerm, FieldIndexing.Analyzed);
}
}
I have no idea why this is happening, the only work around i have is to return the result unordered. Can anyone spot what the problem is here ?
Thanks

You probably don't want the orderby to work. The result of a [full text] Search is going to be ordered by Lucene score. That's going to give you the best matches for the search terms provided by the user. Given that, ordering by publish date would "ruin" the quality of the results.
However, I just tried this with v30k, and it appears to use the order by properly after filtering using Search.
Edit - I notice you're using "SearchTerm" for the query model and the analyze expression, but you're indexing "SearchQuery". Make those the same and it should work.

Related

RavenDB: How to properly query/filter a nested value from a MultiMapIndex?

My application has a requirement that is should be able to filter/search for Pairs by the Number of the related Contact.
A Pair always has a reference to a Contact stored, but the number of the contact is not, and will not, be stored in the reference. So I tried to create a custom index for this, because the Pair and Contact are stored in different collections.
A simplified example of the index looks like this.
public class Pairs_Search : AbstractMultiMapIndexCreationTask<Pairs_Search.Result>
{
public class Result
{
public string Id { get; set; }
public string Workspace { get; set; }
public ContactResult Contact { get; set; }
public bool HasContactDetails { get; set; }
}
public class ContactResult
{
public string Id { get; set; }
public string Name { get; set; }
public int Number { get; set; }
}
public Pairs_Search()
{
AddMap<Pair>(pairs => pairs
.Select(p => new
{
p.Id,
p.Workspace,
Contact = new
{
p.Contact.Id,
p.Contact.Name,
Number = 0
},
// Mark this items as WITHOUT contact details.
HasContactDetails = false,
}
)
);
AddMap<Contact>(contacts => contacts
.Select(c => new
{
Id = (string) null,
Workspace = (string) null,
Contact = new
{
c.Id,
Name = c.DisplayName,
c.Number
},
// Mark this items as WITH contact details.
HasContactDetails = true,
}
)
);
Reduce = results => results
// First group by the contact ID. This will
// create a group with 2 or more items. One with the contact
// details, and one or more with pair details.
// They are all marked by a boolean flag 'HasContactDetails'.
.GroupBy(x => x.Contact.Id)
// We are going to enrich each item in the current group, that is
// marked as 'HasContactDetails = false', with the contact number.
// We need that so that we can filter on it later.
.Select(group =>
group
.Select(i => new
{
i.Id,
i.Workspace,
Contact = new
{
i.Contact.Id,
i.Contact.Name,
// Does the current item have the contact details?
Number = i.HasContactDetails
// Yes, in this case we use the previously set contact number.
? i.Contact.Number
// No, find the item with the contact details and grab the number.
: group.Single(x => x.HasContactDetails).Contact.Number
},
// Pass on the flag that indicates wheter or not
// this item has the contact details. We are going
// to need it later.
i.HasContactDetails
}
)
// We don't need the items with the contact details
// anymore, so filter them out.
.Where(x => !x.HasContactDetails)
)
// Flatten all the small lists to one big list.
.SelectMany(x => x);
// Mark the following fields of the result as searchable.
Index(x => x.Contact.Number, FieldIndexing.Search);
}
}
I've setup a full example that reproduces the issues I am having. You can find the example here.
Creating the index works fine. Querying the index works fine also as it properly matched the pair and contact and enriched the index result with the number of the contact. But when I try to use a .Where() or .Search() on the nested Number property it fails to properly filter the result dataset from the index.
The index without any filtering works as you can see in below code example (also available in the full example).
private static async Task ThisOneWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.ToListAsync();
LogResults("ThisOneWorks()", results);
}
// Output:
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Hermione Granger' with number '71'
// ThisOneWorks(): Pair 'Albus Dumbledore' with number '72'
}
Filtering on a non-nested value also works (also available in the full example). As you can see it filters out the one with a different workspace.
private static async Task ThisOneWithWorkspaceFilterWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.ToListAsync();
LogResults("ThisOneWithWorkspaceFilterWorks()", results);
}
// Output:
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Hermione Granger' with number '71'
}
When I try to filter/search on the Workspace and Number properties I would expect two results that are related to the contact Harry Potter. But instead I just get an empty dataset back.
private static async Task ThisOneWithWorkspaceAndNumberFilterDoesntWork()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.Where(x => x.Contact.Number == 70)
.ToListAsync();
LogResults("ThisOneWithWorkspaceAndNumberFilterDoesntWork()", results);
}
// Output:
// ThisOneWithWorkspaceAndNumberFilterDoesntWork(): EMPTY RESULTS!
}
Can anyone tell me what I am doing wrong here? Any help would be greatly appreciated!
The way to go about it is to store ContactResult in a different collection,
which is what is called a related document in this case,
and when you create the index then you 'Index the Related Document'
Learn from the demo example in:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
The example is for a basic map index but the principle is the same for Multi-Map.
Remove the public class ContactResult from the index class
and define the index with something like:
select new Result
{
....
Number = LoadDocument<Contact>(Pair.Contact).Number
....
}

How can I select the root entity and child entity count using QueryOver in NHibernate?

I have a pretty simple query I'm trying to convert to NHibernate's QueryOver syntax, but I'm having difficulty. The original SQL query is functionally the same as:
SELECT [Post].*, (
SELECT Count(*)
FROM [Comment]
WHERE [Comment].[PostId] = [Post].[Id]) AS [CommentCount]
FROM [Post]
The problem is I'm having difficulty converting this to QueryOver syntax. I tried defining a summary class containing both the Post and the CommandCount as such:
public class PostSummary
{
public Post Post { get; set; }
public CommentCount { get; set; }
}
And then defining the query with a couple of selects:
Post lPostAlias = null;
Comment lCommentAlias = null;
var lCommentSubquery = QueryOver.Of(() => lCommentAlias)
.Where(() => lCommentAlias.Post.Id == lPostAlias.Id)
.ToRowCountQuery();
PostSummary lPostSummaryAlias = null;
session.QueryOver(() => lPostAlias)
.SelectList(list => list
.Select(x => x).WithAlias(() => lSummary.Post)
.SelectSubQuery(lCommentSubQuery).WithAlias(() => lSummary.CommentCount)
.List<PostSummary>();
An exception gets thrown with the error message:
could not resolve property: of: Project.Models.Post
So it looks like it doesn't like the .Select(x => x) part of the query. I was hoping to find something along the lines of 'Projections.RootEntity()` but alas there is no such thing that I can find.
Can someone explain what I'm doing wrong and guide me to the proper way to do this basic query? I imaging I could select all the properties of Post that I want, but worry that I'll lose the ability to take advantage of the proxy sub-classes NHibernate generates for lazy-loading purposes and is not what I want.
using the LINQ provider you can write
var query = from post in session.Query<Post>()
select new PostSummary { Post = post, CommentCount = post.Comments.Count };
return query.ToList();

Is Boosting whole documents supported in an AbstractMultiMapIndexCreationTask<T> that utilizes the Reduce feature?

I'm trying to boost documents based on how recent they were posted as discussed in this answer.
The following index definition works fine creating an index that is populated with Article and Recipe entities.
public class TestIndex : AbstractMultiMapIndexCreationTask<Result>
{
public TestIndex()
{
AddMap<Article>(docs => from doc in docs
where !doc.IsDeleted
select new Result
{
Id = doc.Id,
Title = doc.Title,
DatePublished = doc.DatePublished
}.Boost(doc.DatePublished.Ticks / 1000000f));
AddMap<Recipe>(docs => from doc in docs
where !doc.IsDeleted
select new Result
{
Id = doc.Id,
Title = doc.Title,
DatePublished = doc.DatePublished
}.Boost(doc.DatePublished.Ticks / 100000f));
}
public override string IndexName
{
get { return "Tests/WithBoost"; }
}
}
When I try to add a Reduce to store the documents as a Result item the index ceases to generate any results.
public class TestIndex : AbstractMultiMapIndexCreationTask<Result>
{
public TestIndex()
{
AddMap<Article>(docs => from doc in docs
where !doc.IsDeleted
select new Result
{
Id = doc.Id,
Title = doc.Title,
DatePublished = doc.DatePublished
}.Boost(doc.DatePublished.Ticks / 100000f));
AddMap<Recipe>(docs => from doc in docs
where !doc.IsDeleted
select new Result
{
Id = doc.Id,
Title = doc.Title,
DatePublished = doc.DatePublished
}.Boost(doc.DatePublished.Ticks / 100000f));
Reduce = docs => from doc in docs
group doc by doc.Id into g
select new Result
{
Id = g.First().Id,
Title = g.First().Title,
DatePublished = g.First().DatePublished
};
}
public override string IndexName
{
get { return "Tests/WithBoost"; }
}
}
Is boosting documents supported when using the Reduce feature of AbstractMultiMapIndexCreationTask<T>?
A thought I'm having is that Boost() returns a BoostedValue, so is the collection being passed to the Reduce expression actually IEnumerable<BoostedValue> instead of IEnumerable<Result>, and therefore the Reduce expression cannot be compiled against the input?
(This answer carried over from the comments in the original post)
If you just want to sort by date published, don't use boost - use .OrderByDescending(x=> x.DatePublished).
If you're just wanting to know how to query on a combined multimap result, you can do that easily like this:
var results = session.Query<Result, TestIndex>()
.Search(x=> x.Title, "whatever")
.As<object>()
.ToList();
var articles = results.OfType<Article>();
var recipies = results.OfType<Recipe>();
I'm assuming you want to search, since you are boosting. You should also be marking the searchable fields as analyzed, as documented here.
I'm also assuming you want articles and recipes separated into separate lists. If you don't, then you can just use the object list directly. Or if you have some base class or interface that they both use, then you can use that instead of object, as in .As<IWhatever>().

RavenDb: Join data with index

In my database I have a list of cases:
{ Id: 1, Owner: "guid1", Text: "Question1" }
{ Id: 2, Owner: "guid1", Text: "Question2" }
{ Id: 3, Owner: "guid2", Text: "Question3" }
When querying for data I would also like to have (in my index, result) number of cases each Owner has. So I created a map/reduce index on this collection:
public class RelatedCases
{
public Guid Owner { get; set; }
public int Count { get; set; }
}
public class RelatedCaseIndex : AbstractMultiMapIndexCreationTask<RelatedCases>
{
public RelatedCaseIndex()
{
AddMap<CaseDocument> (c => c.Select(a => new { a.Owner, Count = 1 }));
Reduce = result => result
.GroupBy(a => a.Owner)
.Select(a => new
{
Owner = a.Key,
Count = a.Sum(b => b.Count)
});
}
}
Now I just have no idea how to produce a query to include the data from the index. Based on documentation I tried something like:
session.Query<CaseDocument>().Customize(a => a.Include ...)
or TransformResults on a CaseIndex, which didn't work out properly.
I know I could just requery raven to get me list of all RelatedCases in a separate query, but I would like to do it in one round-trip.
You can't query for Cases and join the result with the map/reduce index on the fly. That's just not how it works, because every query will run against an index, so what you are really asking is joining two indexes. This is something you need to do upfront.
In other words - put all the information you want to query upon into your map/reduce index. You can then run the query on this index and .Include() the documents that you are also interested in.
I dont think you need a MultiMap index, a simple MapReduce index will suffice for this.
You can then query it like so:
session.Query<RelatedCases, RelatedCaseIndex>();
This will bring back a list of RelatedCases with the owner and count.

ROW_NUMBER() and nhibernate - finding an item's page

given a query in the form of an ICriteria object, I would like to use NHibernate (by means of a projection?) to find an element's order,
in a manner equivalent to using
SELECT ROW_NUMBER() OVER (...)
to find a specific item's index in the query.
(I need this for a "jump to page" functionality in paging)
any suggestions?
NOTE: I don't want to go to a page given it's number yet - I know how to do that - I want to get the item's INDEX so I can divide it by page size and get the page index.
After looking at the sources for NHibernate, I'm fairly sure that there exists no such functionality.
I wouldn't mind, however, for someone to prove me wrong.
In my specific setting, I did solve this problem by writing a method that takes a couple of lambdas (representing the key column, and an optional column to filter by - all properties of a specific domain entity). This method then builds the sql and calls session.CreateSQLQuery(...).UniqueResult(); I'm not claiming that this is a general purpose solution.
To avoid the use of magic strings, I borrowed a copy of PropertyHelper<T> from this answer.
Here's the code:
public abstract class RepositoryBase<T> where T : DomainEntityBase
{
public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector, TWhere whereValue) where TWhere : DomainEntityBase
{
if (entity == null || entity.Id == Guid.Empty)
{
return -1;
}
var entityType = typeof(T).Name;
var keyField = PropertyHelper<T>.GetProperty(uniqueSelector).Name;
var keyValue = uniqueSelector.Compile()(entity);
var innerWhere = string.Empty;
if (whereSelector != null)
{
// Builds a column name that adheres to our naming conventions!
var filterField = PropertyHelper<T>.GetProperty(whereSelector).Name + "Id";
if (whereValue == null)
{
innerWhere = string.Format(" where [{0}] is null", filterField);
}
else
{
innerWhere = string.Format(" where [{0}] = :filterValue", filterField);
}
}
var innerQuery = string.Format("(select [{0}], row_number() over (order by {0}) as RowNum from [{1}]{2}) X", keyField, entityType, innerWhere);
var outerQuery = string.Format("select RowNum from {0} where {1} = :keyValue", innerQuery, keyField);
var query = _session
.CreateSQLQuery(outerQuery)
.SetParameter("keyValue", keyValue);
if (whereValue != null)
{
query = query.SetParameter("filterValue", whereValue.Id);
}
var sqlRowNumber = query.UniqueResult<long>();
// The row_number() function is one-based. Our index should be zero-based.
sqlRowNumber -= 1;
return sqlRowNumber;
}
public long GetIndexOf<TUnique>(T entity, Expression<Func<T, TUnique>> uniqueSelector)
{
return GetIndexOf(entity, uniqueSelector, null, (DomainEntityBase)null);
}
public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector) where TWhere : DomainEntityBase
{
return GetIndexOf(entity, uniqueSelector, whereSelector, whereSelector.Compile()(entity));
}
}
public abstract class DomainEntityBase
{
public virtual Guid Id { get; protected set; }
}
And you use it like so:
...
public class Book : DomainEntityBase
{
public virtual string Title { get; set; }
public virtual Category Category { get; set; }
...
}
public class Category : DomainEntityBase { ... }
public class BookRepository : RepositoryBase<Book> { ... }
...
var repository = new BookRepository();
var book = ... a persisted book ...
// Get the index of the book, sorted by title.
var index = repository.GetIndexOf(book, b => b.Title);
// Get the index of the book, sorted by title and filtered by that book's category.
var indexInCategory = repository.GetIndexOf(book, b => b.Title, b => b.Category);
As I said, this works for me. I'll definitely tweak it as I move forward. YMMV.
Now, if the OP has solved this himself, then I would love to see his solution! :-)
ICriteria has this 2 functions:
SetFirstResult()
and
SetMaxResults()
which transform your SQL statement into using ROW_NUMBER (in sql server) or limit in MySql.
So if you want 25 records on the third page you could use:
.SetFirstResult(2*25)
.SetMaxResults(25)
After trying to find an NHibernate based solution for this myself, I ultimately just added a column to the view I happened to be using:
CREATE VIEW vw_paged AS
SELECT ROW_NUMBER() OVER (ORDER BY Id) AS [Row], p.column1, p.column2
FROM paged_table p
This doesn't really help if you need complex sorting options, but it does work for simple cases.
A Criteria query, of course, would look something like this:
public static IList<Paged> GetRange(string search, int rows)
{
var match = DbSession.Current.CreateCriteria<Job>()
.Add(Restrictions.Like("Id", search + '%'))
.AddOrder(Order.Asc("Id"))
.SetMaxResults(1)
.UniqueResult<Paged>();
if (match == null)
return new List<Paged>();
if (rows == 1)
return new List<Paged> {match};
return DbSession.Current.CreateCriteria<Paged>()
.Add(Restrictions.Like("Id", search + '%'))
.Add(Restrictions.Ge("Row", match.Row))
.AddOrder(Order.Asc("Id"))
.SetMaxResults(rows)
.List<Paged>();
}