locked
Unable to retrieve Silverlight client projected types QueryOperationResponse. RRS feed

  • Question

  • In trying to take advantage of the new projection capabilities of the latest CTP3 data services library for Silverlight, I noticed that I could successfully enumerate my non-entity projected types from the feed ONLY when I execute individual queries in non-batch mode and enumerate the QueryResult.

    If I pass a DataServiceRequest[] using Uri's generated from the same queries in batch mode (i.e. BeginExecuteBatch), I'm unable to enumerate the QueryOperationResponse<T> returned like I could using the QueryResult in non-batch scenario.  Please see sample code below:

        public partial class Foo  //MY CLIENT SIDE NON-ENTITY TYPE IN "ClientDTOs" NAMESPACE
        {
            short myFooId;
            bool isCustom;
            string name;
            string description;
            public short MyFooId
            {
                get { return myFooId; }
                set { myFooId= value; }
            }
            public string Name
            {
                get { return name; }
                set { name = value; }
            }
            public string Description
            {
                get { return description; }
                set { description = value; }
            }
            public bool IsCustom
            {
                get { return isCustom; }
                set { isCustom = value; }
            }
    
    
    //MY DataServiceQuery:
    
                var Qry = (from vt in FooEntities.GenericFoos
    
                                            select new Foo
                                            {
                                                MyFooId = vt.FooId,
                                                Name = vt.Name,
                                                Description = vt.Description,
                                                IsCustom = false
                                            }) as DataServiceQuery<Foo>;
    
    

     

            public void ExecuteQueryBatch()
            {
                try
                {
                    Context.BeginExecuteBatch(new AsyncCallback(result =>
                        {
                            try
                            {
                                DataServiceResponse Response = Context.EndExecuteBatch(result);
                                if (Response.BatchStatusCode != 202)
                                {
                                    throw (new DataServiceRequestException("Failed to process batch request", null, Response));
                                }
    
                                foreach (QueryOperationResponse Operation in Response)
                                {
                                    if (Operation.Error != null) { throw Operation.Error; }
                                    if (Operation is QueryOperationResponse<Foo>)
                                    {
                                        foreach (Foo vt in Operation as QueryOperationResponse<Foo>)
                                        {
                                            //Does not make it here...receive error like ("The current value 'DataService.GenericFoos' type is not compatible with the expected 'ClientDTOs.Foo' type.")
                                        }
                                     }
                                }
                             }
                            catch (Exception e)
                            {
                                 string ex = e.ToString();
                             }
                        }), null, GetParams()); //GetParams() returns DataServiceRequest[] containing single DataServiceRequest object.
                }
                catch (Exception e)
                {
                    string ex = e.ToString();
                }
            }

    When I try to enumerate the QueryOperationResponse<Foo> in the foreach loop, I receive an error (shown above) after it evaluates the in keyword.  The stacktrace reveals the following:

      at System.Data.Services.Client.DataServiceContext.ResolveTypeFromName(String wireName, Type userType, Boolean checkAssignable)
       at System.Data.Services.Client.MaterializeAtom.GetEntryClientType(String typeName, DataServiceContext context, Type expectedType, Boolean checkAssignable)
       at System.Data.Services.Client.AtomMaterializer.ResolveByCreating(AtomEntry entry, Type expectedEntryType)
       at System.Data.Services.Client.AtomMaterializer.ResolveOrCreateInstance(AtomEntry entry, Type expectedEntryType)
       at System.Data.Services.Client.AtomMaterializer.Materialize(AtomEntry entry, Type expectedEntryType, Boolean includeLinks)
       at System.Data.Services.Client.AtomMaterializer.DirectMaterializePlan(AtomMaterializer materializer, AtomEntry entry, Type expectedEntryType)
       at System.Data.Services.Client.AtomMaterializerInvoker.DirectMaterializePlan(Object materializer, Object entry, Type expectedEntryType)
       at System.Data.Services.Client.ProjectionPlan.Run(AtomMaterializer materializer, AtomEntry entry, Type expectedType)
       at System.Data.Services.Client.AtomMaterializer.Read()
       at System.Data.Services.Client.MaterializeAtom.MoveNextInternal()
       at System.Data.Services.Client.MaterializeAtom.MoveNext()
       at GoVIP.ClientData.SearchModel.<ExecuteQueryBatch>b__0(IAsyncResult result)

    I don't understand why it is unable to recognize the projected type like it does when enumerating the QueryResult from a BeginExecute operation.  I'm Using VS 2008 w/ 3.5 SP1 and EF.  Any assistance is greatly appreciated.

    Regards, Collie

    Monday, March 22, 2010 5:49 PM

Answers

  • Hi,

    The problem is in your GetParams method. Creating a query by specifying its full URI will not get you projections to a different type. This is not supported (and it can't really work all that well even if it would be supported). Please use the approach I have in my sample above. The trick is to create the projection query using the LINQ syntax, that way the query will not only know the URI (which is constructed from the query), but also the type information and more importantly the mapping between the properties and so on. The query returned from the LINQ syntax is a class which can be converted to DataServiceQuery<T> (with the right T of course). So just cast it and add that to your array of queries for batch processing.

    Thanks,


    Vitek Karas [MSFT]
    • Marked as answer by Collie G Tuesday, March 23, 2010 11:51 AM
    Tuesday, March 23, 2010 9:46 AM
    Moderator

All replies

  • Hi,

    I'm sorry, I just tried this and it works just fine. Could you please show us your GetParams() method.

    I know my sample is very simplified, but it should run pretty much the same code as you use.

        public class A
        {
            public A()
            {
                this.Name = "";
            }
    
            public string Name { get; set; }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                DemoService ctx = new DemoService(new Uri("http://services.odata.org/OData/OData.svc/"));
    
                var q = ctx.Products.Select(p => new A { Name = p.Name });
                DataServiceQuery dsq = q as DataServiceQuery;
                foreach (var response in ctx.ExecuteBatch(dsq))
                {
                    foreach (A i in (IEnumerable<A>)response)
                    {
                        Console.WriteLine(i.Name);
                    }
                }
            }
        }
    

    Thanks,


    Vitek Karas [MSFT]
    Monday, March 22, 2010 8:46 PM
    Moderator
  • Hello, and thanks for taking a look at this.  Currently, the GetParams() returns a DataServiceRequest[] w/ a single hard coded DataServiceRequest object:

            public DataServiceRequest[] GetParams()
            {
                DataServiceRequest[] Params = new DataServiceRequest[1] { new DataServiceRequest<Foo>(new Uri("http://localhost/FooDataService.svc/GenericFoos()?$select=FooId,Name,Description")) };
                return Params;
            }
    
    

     

    If I execute w/o using projection and simply query complete entities all is well.  Thanks again...

     

    Tuesday, March 23, 2010 12:20 AM
  • Hi Collie,

     Can you share the complete stack trace for this error : "//Does not make it here...receive error like ("The current value 'DataService.GenericFoos' type is not compatible with the expected 'ClientDTOs.Foo' type.")
    " ?

     


    Phani Raj Astoria http://blogs.msdn.com/PhaniRaj
    Tuesday, March 23, 2010 1:56 AM
    Moderator
  • Hi,

    The problem is in your GetParams method. Creating a query by specifying its full URI will not get you projections to a different type. This is not supported (and it can't really work all that well even if it would be supported). Please use the approach I have in my sample above. The trick is to create the projection query using the LINQ syntax, that way the query will not only know the URI (which is constructed from the query), but also the type information and more importantly the mapping between the properties and so on. The query returned from the LINQ syntax is a class which can be converted to DataServiceQuery<T> (with the right T of course). So just cast it and add that to your array of queries for batch processing.

    Thanks,


    Vitek Karas [MSFT]
    • Marked as answer by Collie G Tuesday, March 23, 2010 11:51 AM
    Tuesday, March 23, 2010 9:46 AM
    Moderator
  • This was my problem as once I converted to Linq syntax all was well.  Thanks so much for pointing this out to me and being so helpful!

     

    Regards, Collie

    Tuesday, March 23, 2010 11:54 AM
  • I'm a newbie to WCF data services. So I don't understand your explanation. I run in the same error as Collie G. Here is my code for the query generation:

     

     

    Dim GetPredefinedBookletTitleQuery = (From c In ctx.Booklet

     

     

                                          Where c.Title.StartsWith("Booklet")

     

     

                                          Order By c.Title

     

     

                                          Select New PredefinedBookletTitle With {.Title = c.Title})

    Dim GetPredefinedBookletTitleRequest As New DataServiceRequest(Of PredefinedBookletTitle)(CType(GetPredefinedBookletTitleQuery, DataServiceQuery).RequestUri)

    And here is the utilization of the query result:

     

    For

     

     

    Each qoResponse As QueryOperationResponse In batchOperationResponse

     

     

        If qoResponse IsNot Nothing Then

     

     

     

     

     

            For Each b In CType(qoResponse, IEnumerable(Of PredefinedBookletTitle)) <= the error occurs here 

                    '.... do something 

     

     

            Next

     

     

        End If

     

     

    Next

     The error message is:

    The current value 'SilverBook.SilverBookService.SilverBookModel.Booklet' type is not compatible with the expected 'SilverBook.NewBooklet+PredefinedBookletTitle' type.

     

    Monday, April 5, 2010 6:32 PM
  • Hi,

    Instead of creating a new DataServiceRequest from the URI constructed from your LINQ query, simply cast the LINQ query to an instance of DataServiceQuery(Of T) and use its BeginExecute method to run it. Once it's finished call its EndExecute method which will return an IEnumerable(Of T) to you which you enumerate over to get the results.

    When you create a new DataServiceRequest from the URI you loose the information of how to materialize the results on the client, almsot the entire Select ... part of your LINQ query. So the client will try to do its best to figure out the types to create, but it doesn't have enough information to do so in your case.

    Thanks,


    Vitek Karas [MSFT]
    Tuesday, April 6, 2010 10:09 AM
    Moderator