Ayende @ Rahien

My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:


+972 52-548-6969

, @ Q c

Posts: 6,124 | Comments: 45,475

filter by tags archive

Orders Search in RavenDB

time to read 6 min | 1055 words

One of the things that we have been working on recently is our internal ordering system. We did a major upgrade on how it works, and along the way, we moved it to RavenDB. This post is to talk specifically about one feature in the system, Order Search.

As you can probably guess, order search is something that is quite important. It is annoying to an extent, because users come to us with all sort of information about an order, from “I ordered last year some stuff” to “with order id E12312312”. The later is very easy, the former tends to be somewhat of a challenge to find.

So I set out to try to figure out if we can do something better than just the standard gigantic search screen. For example, here is how you can search an order using Plimus:


There is way too much stuff there. Not to mention that the backend of such a screen is likely complex. Instead, I choose to go with a different metaphor:


The question is, how to do it?

It turns out that this is really quite simple to do with RavenDB, here is the full index:

public class Orders_Search : AbstractIndexCreationTask<Order, Orders_Search.ReduceResult>
    public class ReduceResult
        public string Query { get; set; }
        public DateTime LastPaymentDate { get; set; }

    public Orders_Search()
        Map = orders => from order in orders
                        select new
                            Query = new object[]
                                order.Payments.Select(payment => payment.PaymentIdentifier),
                            LastPaymentDate = order.Payments.Last().At

And here is the UI for that:


But the question here is, how does this work?

Well, it turns out that RavenDB has some really interesting behavior when you feed it an IEnumerable. Instead of trying to index the IEnumerable as a single value, it index that as a multi value field.

That means that for each order, we are going to actually store the following data in the Query field:

  • Ayende  (FirstName)
  • Rahien   (LastName)
  • E11111111 (Order Number)
  • ayende@ayende.com (Email)
  • ayende (Email.Split(‘@’) first part)
  • ayende.com (Email.Split(‘@’) second part)
  • Hibernating Rhinos (Company)
  • E111111 (PaymentIdentifier for first payment)
  • E111234  (PaymentIdentifier for second payment)
  • E123412 (PaymentIdentifier for third payment)
  • EFA32826-1E09-48FA-BC0E-9A9AAF0FDD70 (LicenseIds#1)
  • 95CDDED2-2D19-48AF-991D-1284446CB7A3 (LicenseIds #2)

Because we store all of those values inside a single field, we can query for either one of them. Hell, we can even enable full text search on the field and allow even more interesting searches.

And the best thing, it pretty much Just Works. The user can put anything that might identify the order, and we will figure it out for him. And since the user is often me, I am very happy about it.



What I don't understand from this is how you would query this. :(


Nice, I'm just working on something similar.

In your example if someone searched for "Ayende Rahien" would they find anything, or would you need to add an additional field to the index?

Nic Wise

I did the same for our product search, which also happens to have a lot of Japanese in it. Having read this, I'm going to revise it, but this is what I came up with:

from prod in docs.Products let FullText = prod.ArtistName + " " + prod.ProductTitleJapanese + " " + prod.Label + " " + prod.ParentLabel select new { FullText }

And then did a full text index on FullText. Works great so far. Joy of having an engine like Lucene under the hood!

Ayende Rahien


session.Query<OrdersSearch.ReduceResult, OrdersSearch>() .Where(x=>x.Query == "foo") .As()

Ayende Rahien

Nic, The array method is more efficient way of handily that, and it gives you better options for handling things

Ayende Rahien

Rob, In my example, if they search with the quotes, they won't find it. Without the quotes, they would


Nice example. How does the date filtering work though? it looks like thats being dropped. Say you only want records where the last payment is between one date and another? Is that something you could add as a second query field??


JSON is a text format. Just put that text into lucene index (analyzed/tokenized) and you'll have full document text search, working out of the box for every document type.

Ayende Rahien

Rafal, Not really no. You don't want to get that sort of this, your relevancy would be pretty low, and your indexes size pretty high.

Daniel Lang

I would not call it "ReduceResult" since there is not reduce operation in this index.


The result type of session.Query<OrdersSearch.ReduceResult, OrdersSearch>() .Where(x=>x.Query == "foo") .As() .ToList(); is a List<OrdersSearch.ReduceResult>, right? and OrdersSearch.ReduceResult.Query type is is a string, right? How do you get the fields to fill your UI table (Order Number, Quantity, ...)?

Ayende Rahien

Andres, that is why we have the As call, it converts the linq queryable to the result type


Thanks for your answer. Now I am having an issue with string.Contains: session.Query<OrdersSearch.ReduceResult, OrdersSearch>() .Where(x=>x.Query.Contains("foo")) .As() .ToList() Generates: Issuing query on index WorkerSearch/Search for: Query:"foo" In place of Query:"foo"

Ayende Rahien

Andres, This isn't the place for this, please ask in the mailing list.


Thanks Oren, I understand now why Contains is not working as I used to think.

Could you help me and said me how to "enable full text search on the field and allow even more interesting searches"?


Indexes.Add(x => x.Query, FieldIndexing.Analyzed); below Map in Orders_Search constructor is not working for me.

Ayende Rahien

Andres, Please continue this in the mailing list.

Comment preview

Comments have been closed on this topic.


  1. The design of RavenDB 4.0: Making Lucene reliable - 14 hours from now
  2. RavenDB 3.5 whirl wind tour: I’ll find who is taking my I/O bandwidth and they SHALL pay - about one day from now
  3. The design of RavenDB 4.0: Physically segregating collections - 3 days from now
  4. RavenDB 3.5 Whirlwind tour: I need to be free to explore my data - 4 days from now
  5. RavenDB 3.5 whirl wind tour: I'll have the 3+1 goodies to go, please - 7 days from now

And 13 more posts are pending...

There are posts all the way to May 30, 2016


  1. RavenDB 3.5 whirl wind tour (14):
    02 May 2016 - You want all the data, you can’t handle all the data
  2. The design of RavenDB 4.0 (13):
    28 Apr 2016 - The implications of the blittable format
  3. Tasks for the new comer (2):
    15 Apr 2016 - Quartz.NET with RavenDB
  4. Code through the looking glass (5):
    18 Mar 2016 - And a linear search to rule them
  5. Find the bug (8):
    29 Feb 2016 - When you can't rely on your own identity
View all series



Main feed Feed Stats
Comments feed   Comments Feed Stats