DataServiceQuery<T>.Expand


ADO.Net Data Services allows you to expose your LINQ To Entities model (or LINQ To SQL model, or even your custom IQueryable model) via a RESTful API with minimal coding. For example, if you’re working with the Northwind database you can use the URL http://server/Service.svc/Customers(‘ALFKI’)/Orders to retrieve the orders for customer ALFKI. This simplicity makes it easy to retrieve data in a variety of scenarios – you simply need to be able to issue HTTP requests to access the data. To make it really easy to consume the data from client-side javascript, you can even specify that you want the data to be in JSON format. In rich client scenarios, you can either work with HTTP requests or if you like to make things really easy you can create a LINQ data model to access your ADO.Net Data Service. Simply point DataSvcUtil at your service and it will generate the LINQ classes you need. There’s a very good article by Shawn Wildermuth in the September 2008 issue of MSDN Magazine called ‘Creating Data-Centric Web Applications With Silverlight 2’ [1] that takes you through the process of creating a service and the LINQ classes to access the service from Silverlight. The article also discusses the mechanisms that ADO.Net Data Services uses to discover your entities if you want to expose your custom model (or if you’re just interested in how it works). I’ve also included a few extra links in the references at the end of the post if you want to find out more. Now, back to the point of the post...

The client-side code can use the DataServiceQuery<T>.Expand method to specify what properties should also be retrieved (much like the Include method in my last post Improving ObjectQuery<T>.Include). So you can write the following query to retrieve the customer ALFKI from the Northwind database and also pull back the orders:

var q = from c in context.Customers.Expand("Orders/Order_Details")
        where c.CustomerID == "ALFKI" select c;

Notice the call to the Expand Method. Much like the Include method for LINQ To Entities (see my last post), this lets yoe specify that you want certain related data to be pre-fetched. If you wanted to get the order details included as well then you could change the query to:

var q = from c in context.Customers.Expand("Orders/Order_Details")
        where c.CustomerID == "ALFKI" select c;

One point to notice is that it uses a forward slash (‘/’) rather than a decimal (‘.’) as the path separator, other than that it’s pretty similar to Include. Applying the same logic as in my last post, we can quickly create extension methods that allow us to write the above queries as

var q = from c in context.Customers.Expand(c => c.Orders)
        where c.CustomerID == "ALFKI" select c;

and

var q = from c in context.Customers.Expand(c => c.Orders.SubExpand(o=> o.Order_Details))
        where c.CustomerID == "ALFKI" select c;

Note that you can also expand multiple properties on the same entity. So if you were querying for orders, you can also bring back the customer and order details:

var q = from o in context.Orders.Expand(o=>o.Customer).Expand(o=>o.Order_Details)
        where o.Customer.CustomerID == "ALFKI" select o;

The code for the extension method is shown below – note that the code isn’t production ready (and as always, is subject to the standard disclaimer: “These postings are provided "AS IS" with no warranties, and confer no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm”). Since this code is very similar to my previous post, and gives you the same benefits, I’ll refer you to that post if you want to know how the code works and what the benefits are!

public static class DataServiceQueryExtensions {
    public static DataServiceQuery<TSource> Expand<TSource, TPropType>(this DataServiceQuery<TSource> source, Expression<Func<TSource, TPropType>> propertySelector)
    {
        string expandString = BuildString(propertySelector);
        return source.Expand(expandString);
    }
    private static string BuildString(Expression propertySelector)
    {
        switch (propertySelector.NodeType)
        {
            case ExpressionType.Lambda:
                LambdaExpression lambdaExpression = (LambdaExpression)propertySelector;
                return BuildString(lambdaExpression.Body);

            case ExpressionType.Quote:
                UnaryExpression unaryExpression = (UnaryExpression)propertySelector;
                return BuildString(unaryExpression.Operand);

            case ExpressionType.MemberAccess:
                MemberInfo propertyInfo = ((MemberExpression)propertySelector).Member;
                return propertyInfo.Name;

            case ExpressionType.Call:
                MethodCallExpression methodCallExpression = (MethodCallExpression)propertySelector;
                if (IsSubExpand(methodCallExpression.Method)) // check that it's a SubExpand call {
                    // argument 0 is the expression to which the SubExpand is applied (this could be member access or another SubExpand) // argument 1 is the expression to apply to get the expanded property // Pass both to BuildString to get the full expression return BuildString(methodCallExpression.Arguments[0]) + "/" +
                           BuildString(methodCallExpression.Arguments[1]);
                }
                // else drop out and throw break;
        }
        throw new InvalidOperationException("Expression must be a member expression or an SubExpand call: " + propertySelector.ToString());

    }

    private static readonly MethodInfo[] SubExpandMethods;
    static DataServiceQueryExtensions()
    {
        Type type = typeof(DataServiceQueryExtensions);
        SubExpandMethods = type.GetMethods().Where(mi => mi.Name == "SubExpand").ToArray();
    }
    private static bool IsSubExpand(MethodInfo methodInfo)
    {
        if (methodInfo.IsGenericMethod)
        {
            if (!methodInfo.IsGenericMethodDefinition)
            {
                methodInfo = methodInfo.GetGenericMethodDefinition();
            }
        }
        return SubExpandMethods.Contains(methodInfo);
    }

    public static TPropType SubExpand<TSource, TPropType>(this Collection<TSource> source, Expression<Func<TSource, TPropType>> propertySelector)
        where TSource : class where TPropType : class {
        throw new InvalidOperationException("This method is only intended for use with DataServiceQueryExtensions.Expand to generate expressions trees"); // no actually using this - just want the expression! }
    public static TPropType SubExpand<TSource, TPropType>(this TSource source, Expression<Func<TSource, TPropType>> propertySelector)
        where TSource : class where TPropType : class {
        throw new InvalidOperationException("This method is only intended for use with DataServiceQueryExtensions.Expand to generate expressions trees"); // no actually using this - just want the expression! }
}

 

References:

1: Creating Data-Centric Web Applications With Silverlight 2, Shawn Wildermuth. MSDN Magazine, September 2008. http://msdn.microsoft.com/en-us/magazine/cc794279.aspx

2 ADO.Net Data Services team blog. http://blogs.msdn.com/astoriateam/

3 ADO.Net Data Services homepage on MSDN. http://msdn.microsoft.com/en-us/data/bb931106.aspx

4 Mike Taulty has a number of blog posts on ADO.Net Data Services. http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/category/1027.aspx

DataServiceQueryExpand - blog.zip

Comments (7)

  1. GaxUser says:

    Great stuff. One question though. Why does intellisence not work in the case of a collection?

    i.e.

    var q = from c in context.Customers.Expand(c => c.Orders.SubExpand(o=> o.

    no intellisense?

    var q = from c in context.Customers.Expand(c => c.Orders.SubExpand(o=> (Order)o.

    works

  2. stuartle says:

    Hi GaxUser,

    I just created a test project in Visual Studio 2010 and it seemed to be working fine without the cast, so I'm not sure why it isn't working in your project.

    – Stuart

  3. ScottH says:

    This code has been posted in multiple places online and it works great except with SubExpand.  I read your articles on "Improving ObjectQuery<T>.Include" and "Improving ObjectQuery<T>.Include – Updated" and I tried making the same modification to the DataServiceQueryExtensions class but it didn't change the outcome.

    I keep getting this error: "The expression 's => s.Entity.SubExpand(p => p.OtherEntity)' is not a valid expression for navigation path. The only supported operations inside the lambda expression body are MemberAccess and TypeAs. The expression must contain at least one MemberAccess and it cannot end with TypeAs."

    Any ideas?

    -Scott

  4. stuartle says:

    Hi Scott,

    I've just tested this with the code below and it is worked fine

       from wibble in context.Wibbles.Expand(w => w.Bibble.SubExpand(b => b.Dibble))

       select wibble;

    Can you share a bit more of the code snippet that you're using?

    Thanks,

    Stuart

  5. stuartle says:

    Scott – I've also added a sample app 🙂

  6. Jean-Marie Pirelli says:

    I guess Scott is using a newer version of Data Services which has an Expand method, so the extension method is not used. I renamed it to ExpandEx, and it is now used by the compiler.

    I changed the first override of SubExpand, so that it complements ICollection (instead of Collection) and now it works great in my environment, thanks ! Maybe a change to IEnumerable<> would be even better.

Skip to main content