Querying a child collection by multiple values in

2019-02-10 00:42发布

问题:

I'm using RavenDB build 371 and I have the following model:

class Product {
 public string Id { get; set; }
 public ProductSpec[] Specs { get; set; }
}

class ProductSpec {
 public string Name { get; set; }
 public string Value { get; set; }
}

I would like to be able to query for products which have a set of specs. When querying by a single spec:

session.Query<Product>()
 .Where(product => product.Specs.Any(spec => spec.Name == "Color" && spec.Value == "Red"))
 .ToList();

The expected results are returned, however when an additional spec predicate is added:

session.Query<Product>()
 .Where(product => product.Specs.Any(spec => spec.Name == "Color" && spec.Value == "Red"))
 .Where(product => product.Specs.Any(spec => spec.Name == "Country" && spec.Value == "US"))
 .ToList();

no results are returned even though the results returned by the first query contain products with spec name "Country" and spec value "US". The same outcome is observed when using the LuceneQuery method. This seems to be a similar issue to this discussion however I was unable to implement to suggested solution. Specifically, after creating the suggested index, I don't know how to query it.

How can I support this type of query in RavenDB?

EDIT

I still can't query on multiple values on a collection of compound types. Instead, I changed the model so that a spec/value combination is a concatenated string such that the specs collection is an array of strings. This can be queried by multiple values:

class Product {
 public string Id { get; set; }
 public int CategoryId { get; set; }
 public string[] Specs { get; set; }
}

For reference, the original model and query works when using MongoDB with their multikeys index feature. The very surprising problem with MongoDB is that the count() operation is slow for index queries. This type of query is essential for pagination and although count can be cached I would like a solution which provides this out of the box. Also, one other requirement I have is the ability to aggregate spec groups for arbitrary collections of products (for example, to get a collection of all spec/value combinations for products in a given category). In MongoDB this can be achieved using their MapReduce functionality, however the results of a MapReduce operation are static and must be manually updated when the source data changes whereas RavenDB updates MapReduce indexes automatically in the background. So, even though declaring MapReduce indexes in RavenDB is more cumbersome than it is in MongoDB IMO, the automatic background updating outweighs the drawbacks by a long shot. I will be looking at CouchDB as their views are also updated automatically, though it appears they are updated on demand, not automatically in the background, not sure if this will be an issue.

回答1:

I have tried different things, and could not make it work either. The specific query you are trying to execute is resolved to this Lucene query by RavenDB (in version 426):

"{(Name:Color AND Value:Red) AND (Name:Country AND Value:US)}" which explains why you get no result.

After googling on the subject, I found this post: Lucene Query Syntax

Different workarounds are suggested among the answers. Hope this will help. Im rather curious myself though, if this really isn't possible.



回答2:

As per build 717 you can do this using the new .Intersect() feature that has been done by Matt Warren. Take a look here: http://issues.hibernatingrhinos.com/issue/RavenDB-51



回答3:

I've changed the model a bit and was able to achieve the desired result using the Project method in AbstractIndexCreationTask. This is the (simplified) data model:

public class Product
{
    public string Id { get; set; }
    public int CategoryId { get; set; }
    public int TotalSold { get; set; }
    public Dictionary<string, string> Specs { get; set; }
}

This is the index definition:

public class Products_ByCategoryIdAndSpecs_SortByTotalSold : AbstractIndexCreationTask<Product>
{
    public Products_ByCategoryIdAndSpecs_SortByTotalSold()
    {
        this.Map = products => from product in products
                               select new
                               {
                                   product.CategoryId,
                                   _ = Project(product.Specs, spec => new Field("Spec_" + spec.Key, spec.Value, Field.Store.NO, Field.Index.ANALYZED)),
                                   product.TotalSold
                               };
    }
}

Then I can query like so:

    var results = session.Advanced.LuceneQuery<Product, Products_ByCategoryIdAndSpecs_SortByTotalSold>()
        .WhereEquals("CategoryId", 15920)
        .AndAlso().WhereEquals("Spec_Class", "3A")
        .AndAlso().WhereEquals("Spec_Finish", "Plain")
        .OrderBy("-TotalSold")
        .ToList(); 

This will return the products in category "15920" which have a "Class" spec value of "3A" and a "Finish" spec value of "Plain" sorted in descending order by the total units sold.

The key was using the Project method which basically creates fields in the Lucene document for each spec name-value pair.



标签: nosql ravendb