Search code examples
wcfwcf-data-servicesravendbodata

How to get RavenDB to work with WCF Data Services?


I use WCF Data Services 5.3.0 with Reflection Data Provider. My source data stored in RavenDB, so all my queries are deferred. So, are there some ways to call .ToList() for my query after OData filter applied and before results returned to client?

UPDATE: My DataModel Property declaration

    public IQueryable<PositionModel> Positions
    {
        get
        {
            using (var session = OLAPDocumentStore.OpenSession())
            {
                return session
                    .Query<ConsolidatedApplicationPosition>()
                    .Select(x => new PositionModel
                                     {
                                         ApplicationId = x.ApplicationId,
                                         CategoryId = x.CategoryId,
                                         ID = x.Id,
                                         Platform = x.Platform,
                                         Position = x.Position,
                                         RegionalCode = x.RegionalCode,
                                         Timestamp = x.Timestamp,
                                         TopListId = x.TopListId
                                     })
                    .AsQueryable();
            }
        }
    }

SOLUTION See @matt-johnson answer, and don't forget to add [DataMember] to all properties in model

[DataContract, DataServiceKey("Id")]
public class PositionModel
{
    [DataMember]
    public string Id { get; set; }
    [DataMember]
    public string ApplicationId { get; set; }
    [DataMember]
    public string CategoryId { get; set; }
    [DataMember]
    public int Position { get; set; }
    [DataMember]
    public string RegionalCode { get; set; }
    [DataMember]
    public DateTime Timestamp { get; set; }
    [DataMember]
    public string TopListId { get; set; }
}

Solution

  • Ok, I had to go refresh my memory about how WCF Data Services works. Shawn Wildermuth has an excellent example video.

    The main problem is that you are trying to transform the query client-side with the Select statement. When you return an IQueryable, that is supposed to hook directly in to the linq provider - in this case, the output of the RavenDB query. You can apply Where and OrderBy filters, but you cannot do a projection with a Select statement. That breaks the query chain.

    So if you want to project from your ConsolidatedApplicationPosition object that you have in the database to the PositionModel object that you are returning to WCF Data Services, you are going to have to do it another way. Here are some options:

    1. If all of the fields that you need projected are already in the source data, then you can just do:

      return session.Query<ConsolidatedApplicationPosition>()
                    .AsProjection<PositionModel>();
      

      The only problem here is that your identifiers don't match up. Raven's default identifier convention is Id (Pascal case) while WCF's is ID (upper case). You can line them up either by changing Raven's convention:

      DocumentStore.Conventions.FindIdentityProperty = x => x.Name == "ID";
      

      or by changing WCF's convention with the DataServiceKey attribute:

      [DataContract, DataServiceKey("Id")]
      public class PositionModel
      {
          [DataMember]
          public int Id { get; set; }
      
          ...
      }
      
    2. If you want to keep the identifiers mismatched, or if you want to transform more fields, you won't be able to use a dynamic index. Instead, you'll have to create a static index and use a transform to control the projection:

      public class YourIndex : AbstractIndexCreationTask<ConsolidatedApplicationPosition>
      {
        public YourIndex()
        {
          Map = docs =>
            from doc in docs
            select new
            {
              // You need to map any fields that you might query
              // or sort on through the odata filters.
            };
      
          TransformResults = (database, docs) =>
            from doc in docs
            select new PositionModel
            {
              // if you need integer identifiers in your result,
              // you'll have to split them from the doc key, like this:
              ID = int.Parse(doc.Id.ToString().Split('/')[1]),
      
              // otherwise, just do this:
              ID = doc.Id,
      
              // bring over all of your other fields as well
              Platform = doc.Platform
              ... etc ...
      
           };
        }
      }
      

      After you have the index in place, you would query it like this:

      return session.Query<PositionModel, YourIndex>();
      

    With either option, there is no select statement in the query, so there's no need to do ToList or AsQueryable. You are simply sending the IQueryable interface of the RavenDB linq provider into the WCF Data Service context.