Search code examples
odataravendb

How to order by last modified in RavenDB?


I'm trying to allow my entities to be ordered by the Last-Modified property in their metadata, using OData query options.

I tried using a transformer as described in Converting to JSON and accessing metadata, but when I apply ODataQueryOptions to the resulting IQueryable, I get an empty array.

The model and view-model:

public class Foo
{
    public int Id { get; set; }
}

public class FooViewModel
{
    public string Id { get; set; }

    public DateTime LastModified { get; set; }
}

The transformer:

public class Foos_WithLastModified : AbstractTransformerCreationTask<Foo>
{
    public Foos_WithLastModified()
    {
        TransformResults = foos => from foo in foos
            let metadata = MetadataFor(foo)
            select new
            {
                Id = foo.Id.ToString(CultureInfo.InvariantCulture),
                LastModified = metadata.Value<DateTime>("Last-Modified")
            };
    }
}

The relevant method in FooController (_session is an IAsyncDocumentSession):

public async Task<ICollection<FooViewModel>> Get(ODataQueryOptions<FooViewModel> options)
{
    var settings = new ODataValidationSettings();
    settings.AllowedOrderByProperties.Add("LastModified");

    options.Validate(settings);

    var foos = _session.Query<Foo>()
        .TransformWith<Foos_WithLastModified, FooViewModel>();
    var odataFoos = (IQueryable<FooViewModel>)options.ApplyTo(foos);
    return await odataFoos.ToListAsync();
}

When I hit /api/Foo, the results are as expected:

[
  {
    "Id": "foos/456",
    "LastModified": "2015-11-23T08:43:10.913662Z"
  },
  {
    "Id": "foos/123",
    "LastModified": "2015-11-23T08:50:34.0907996Z"
  }
]

But when I add OData query options (/api/Foo?$orderby=LastModified), I get an empty array: [].

I also tried changing _session to an IDocumentSession and modifying Get as follows,

[EnableQuery(AllowedOrderByProperties = "LastModified")]
public IQueryable<FooViewModel> Get()
{
    return _session.Query<Foo>()
        .TransformWith<Foos_WithLastModified, FooViewModel>();
}

but I get the same results.

Are transformers the wrong approach? How can I sort by Last-Modified using OData query options?


Solution

  • I do not know how to handle the OData stuff, never tried that, but in order to query for entities, ordered by the metadata value "Last-Modified" using only RavenDB techniques you can do the following:

    Create an index for your entity (in my example a Customer). In this index we add the field LastModified that's using the document's metadata value for Last-Modified.

    public class Customer_ByLastModified : AbstractIndexCreationTask<Customer>
    {
        public class QueryModel
        {
            public DateTime LastModified { get; set; }
        }
    
        public Customer_ByLastModified()
        {
            Map = customers => from customer in customers
                select new
                {
                    LastModified = this.MetadataFor(customer).Value<DateTime>("Last-Modified")
                };
        }
    }
    

    The QueryModel isn't mandatory, but it makes querying via the client API easier, imo. You can then add a Transformer to be able to use the metadata value in your return model:

    public class Customers_WithLastModified : AbstractTransformerCreationTask<Customer>
    {
        public Customers_WithLastModified()
        {
            TransformResults = results => from customer in results
                select new CustomerViewModel
                {
                    Id = customer.Id,
                    Name = customer.Name,
                    LastModified = MetadataFor(customer).Value<DateTime>("Last-Modified")
                };
        }
    }
    

    And then query it like this:

    using (var session = documentStore.OpenSession())
    {
        var customers = session.Query<Customer_ByLastModified.QueryModel, Customer_ByLastModified>()
            .OrderByDescending(x => x.LastModified)
            .TransformWith<Customers_WithLastModified, CustomerViewModel>()
            .ToList();
    }
    

    Hope this helps!