1

I hope it's more clear what I want to do from the code than the title. Basically I am grouping by 2 fields and want to reduce the results into a collection all the ProductKey's constructed in the Map phase.

public class BlockResult
{
    public Client.Names ClientName;
    public string Block;
    public IEnumerable<ProductKey> ProductKeys;
}

public Block()
{
    Map = products =>
            from product in products
            where product.Details.Block != null
            select new
            {
                product.ClientName,
                product.Details.Block,
                ProductKeys = new List<ProductKey>(new ProductKey[]{
                    new ProductKey{
                        Id = product.Id,
                        Url = product.Url
                    }
                })
            };

    Reduce = results =>
            from result in results
            group result by new {result.ClientName, result.Block} into g
            select new BlockResult
            {
                ClientName = g.Key.ClientName,
                Block =  g.Key.Block,
                ProductKeys = g.SelectMany(x=> x.ProductKeys)
            };
}

I get some weird System.InvalidOperationException and a source code dump where basically it is trying to initialize the list with an int (?).

If I try replacing the ProductKey with just IEnumerable ProductIds (and make appropriate changes in the code). Then the code runs but I don't get any results in the reduce.

craastad
  • 6,222
  • 5
  • 32
  • 46

2 Answers2

3

You probably don't want to do this. Are you really going to need to query in this manner? If you know the context, then you should probably just do this:

var q = session.Query<Product>()
               .Where(x => x.ClientName == "Joe" && x.Details.Block == "A");

But, to answer your original question, the following index will work:

public class Products_GroupedByClientNameAndBlock : AbstractIndexCreationTask<Product, Products_GroupedByClientNameAndBlock.Result>
{
    public class Result
    {
        public string ClientName { get; set; }
        public string Block { get; set; }
        public IList<ProductKey> ProductKeys { get; set; }
    }

    public class ProductKey
    {
        public string Id { get; set; }
        public string Url { get; set; }
    }

    public Products_GroupedByClientNameAndBlock()
    {
        Map = products =>
                from product in products
                where product.Details.Block != null
                select new {
                                product.ClientName,
                                product.Details.Block,
                                ProductKeys = new[] { new { product.Id, product.Url } }
                            };

        Reduce = results =>
                    from result in results
                    group result by new { result.ClientName, result.Block }
                    into g
                    select new {
                                g.Key.ClientName,
                                g.Key.Block,
                                ProductKeys = g.SelectMany(x => x.ProductKeys)
                            };
    }
}
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • You are right, this made more sense as a Query and not an index. I ended up doing a query in the end. I kept the question alive to see if my first approach was possible. – craastad Dec 21 '12 at 12:04
0

When replicating I get the same InvalidOperationException, stating that it doesn't understand the index definition (stack trace omitted for brevity).

Url: "/indexes/Keys/ByNameAndBlock" System.InvalidOperationException: Could not understand query:

I'm still not entirely sure what you're attempting here, so this may not be quite what you're after, but I managed to get the following working. In short, Map/Reduce deals in anonymous objects, so strongly typing to your custom types makes no sense to Raven.

public class Keys_ByNameAndBlock : AbstractIndexCreationTask<Product, BlockResult>
{
    public Keys_ByNameAndBlock()
    {
        Map = products =>
              from product in products
              where product.Block != null
              select new
                  {
                      product.Name,
                      product.Block,
                      ProductIds = product.ProductKeys.Select(x => x.Id)
                  };

        Reduce = results =>
                 from result in results
                 group result by new {result.Name, result.Block}
                 into g
                 select new
                     {
                         g.Key.Name,
                         g.Key.Block,
                         ProductIds = g.SelectMany(x => x.ProductIds)
                     };
    }

}

public class Product
{
    public Product()
    {
        ProductKeys = new List<ProductKey>();
    }

    public int ProductId { get; set; }
    public string Url { get; set; }
    public string Name { get; set; }
    public string Block { get; set; }
    public IEnumerable<ProductKey> ProductKeys { get; set; }
}

public class ProductKey
{
    public int Id { get; set; }
    public string Url { get; set; }
}

public class BlockResult
{
    public string Name { get; set; }
    public string Block { get; set; }
    public int[] ProductIds { get; set; }
}
Jaz Lalli
  • 660
  • 7
  • 15
  • ProductKeys was part of the result set. He should not have to modify the source Product to include it. – Matt Johnson-Pint Dec 20 '12 at 14:12
  • Sorry, I started from following the second example mentioned (which was a bit simpler), which involved replacing ProductKey with just the list of IDs. Extending from there to include ID & Url as the ProductKey pretty much results in the same as what's outlined in your answer. Unclear on my part. – Jaz Lalli Dec 20 '12 at 14:48