locked
When and how to use query results RRS feed

  • Question

  • I'm having trouble getting my mind around when I can access query results and how to access them. There seems to be several ways to access the results and each has an appropriate use case. But I haven't written enough diverse code yet to know which is which offhand.

    Here is the code I'm trying to get to work:

    myapp.BrowseQuotes.created = function (screen) {
        msls.promiseOperation(CallGetUserName).then(function PromiseSuccess(PromiseResult) {
            screen.RepUserName = PromiseResult;
            myapp.activeDataWorkspace.aris_dbData.RepByUserName()
                .execute().then(function (results) {
                    var temp = results.results[0].RepFirm.RepFirmID;
                });
        });
    };

    I've created the query RepByUserName in the Query designer and added it to my screen. After I get the username, I assign it to a property, execute the query and then try to access the results.

    If I put a breakpoint on the line where I attempt to access the results and look at Fiddler, the results are being returned just fine:

    But if I try to inspect the results object in Visual Studio, it says it's undefined. And so, of course, my variables and screen properties I'm trying to set are all blank.

    What am I doing wrong in this specific case? And if someone could provide a table as to when to use what, that would also be helpful. I thought I understood the difference between Query().execute() and getQuery() after reading Michael Washinton's book, but I'm clearly still missing something.

    Tuesday, December 1, 2015 7:51 PM

All replies

  • I think the problem is the data is returned 'eventually' but not when you're expecting it.

    Everywhere you have a ".then" it is a "promise" (and they are not guaranteed to execute and return when you want them to) so you have two nested Promises.  Some have gotten this to work but I never have. That is why in my book, in the chapter: Using Promises In Visual Studio LightSwitch You'll notice I will have only one LightSwitch Promise and a JQuery call in that promise but not another nested LightSwitch Promise.

    Anyway, I would use a WCF RIA Service to display the information.


    Unleash the Power - Get the LightSwitch HTML Client / SharePoint book

    http://LightSwitchHelpWebsite.com

    Tuesday, December 1, 2015 9:03 PM
  • Okay, that makes sense. I've done WCF RIA before in the Silverlight client so that shouldn't be a huge problem.

    But let me quickly expand on what I'm trying to do just so that I'm sure this is the correct method. I have a Reps table that has a foreign key to a Firms table. I need the full Rep and Firm entities to copy to a new Quote as the Quote has foreign keys to both tables. I tried to get these in one fell swoop when retrieving the username using your file handlers example. But the JSON Serializer choked on the entity objects.

    Things like the username, email address, and GUID for the Firm worked fine so I figured I would just perform the query client side after I get the username. But, as you can see, that didn't work out. Are WCF RIA Services still the correct method to use? Or is there a better way?

    Tuesday, December 1, 2015 10:39 PM
  • The closes thing I have is this chapter in the book:

    Dynamically Creating Records In The LightSwitch HTML Client

    myapp.AddEditOrder.DynamicAddOrderDetail_execute = function (screen) {
        // Make a new OrderDetail
        var newOrderDetail = new myapp.OrderDetail();
        // Set the Order property
        // Whenever you have associated Entities, there will 
        // be a .set[Entity Name] method available
        newOrderDetail.setOrder(screen.Order);
        // Set the Quantity
        newOrderDetail.OrderQuantity = 0;
        // Try to find a Product
        var Products = screen.details.dataWorkspace.ApplicationData.Products
            .load().then(function (results) {
                // Try to get the first Product
                var FirstProduct = results.results[0];
                // Did we find a first Product?
                if (FirstProduct != undefined && FirstProduct != null) {
                    // Set the first Product as the Product for the OrderDetail
                    newOrderDetail.setProduct(FirstProduct);
                }
            });
    }

    But I would still make an Insert method on a WCF RIA Service and do it there:

    LightSwitch Survey: Handling Complex Business Logic Using WCF RIA Services

    public void UpdateQuestionDetailForUser(QuestionDetailForUser objQuestionDetailForUser)
            {
                // Get the current user
                string strCurrentUserName = System.Web.HttpContext.Current.User.Identity.Name;
                // We are under Forms Authentication so if user is blank then we
                // are debugging and we are TestUser
                if (strCurrentUserName == "")
                {
                    strCurrentUserName = "TestUser";
                }
                // Check for an existing Answer for this Question for this User
                var objSurveyAnswer = (from SurveyAnswers in this.Context.SurveyAnswers
                                       where SurveyAnswers.SurveyQuestion.Id == objQuestionDetailForUser.QuestionId
                                       where SurveyAnswers.UserName == strCurrentUserName
                                       select SurveyAnswers).FirstOrDefault();
                if (objSurveyAnswer != null)
                {
                    try // This is an update ****
                    {
                        // Set values
                        objSurveyAnswer.Choice = Convert.ToInt32(objQuestionDetailForUser.SelectedChoice);
                        objSurveyAnswer.Comment = objQuestionDetailForUser.Comments;
                        // Update LightSwitch Database
                        this.Context.SaveChanges(
                            System.Data.Objects.SaveOptions.DetectChangesBeforeSave);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception("Error inserting QuestionId " + objQuestionDetailForUser.QuestionId, ex);
                    }
                }
                else // This is an Insert ****
                {
                    // Query the GetAllQuestionsForUser method because it calculates if a Question
                    // is Active or not for the QuestionId being inserted
                    // If it is not in the collection, do not allow the insert
                    var objUserQuestion = (from QuestionForUser in this.GetAllQuestionsForUser()
                                           where QuestionForUser.UserName == strCurrentUserName
                                           where QuestionForUser.QuestionId == objQuestionDetailForUser.QuestionId
                                           select QuestionForUser).FirstOrDefault();
                    if (objUserQuestion != null)
                    {
                        try
                        {
                            // Get the Survey Question
                            var objSurveyQuestion = (from SurveyQuestions in this.Context.SurveyQuestions
                                                     where SurveyQuestions.Id == objQuestionDetailForUser.QuestionId
                                                     select SurveyQuestions).FirstOrDefault();
                            // Create a SurveyAnswer object
                            SurveyAnswer objNewSurveyAnswer = this.Context.CreateObject<SurveyAnswer>();
                            // Set values
                            objNewSurveyAnswer.UserName = strCurrentUserName;
                            objNewSurveyAnswer.Choice = Convert.ToInt32(objQuestionDetailForUser.SelectedChoice);
                            objNewSurveyAnswer.Comment = objQuestionDetailForUser.Comments;
                            objNewSurveyAnswer.SurveyQuestion = objSurveyQuestion;
                            // Update LightSwitch Database
                            this.Context.SurveyAnswers.AddObject(objNewSurveyAnswer);
                            this.Context.SaveChanges(
                                System.Data.Objects.SaveOptions.DetectChangesBeforeSave);
                        }
                        catch (Exception ex)
                        {
                            throw new Exception("Error inserting QuestionId " + objQuestionDetailForUser.QuestionId, ex);
                        }
                    }
                    else
                    {
                        throw new Exception("Error inserting Answer. Answer is not marked Active.");
                    }
                }
            }


    Unleash the Power - Get the LightSwitch HTML Client / SharePoint book

    http://LightSwitchHelpWebsite.com

    Tuesday, December 1, 2015 10:48 PM
  • Does CallGetUserName contain all of the operation complete code needed to fulfill the promise operation?  If not, the PromiseOperation will not complete and your variables will be undefined.  Try logging or setting breakpoints to ensure the operation.complete code is executing.

    Does your custom query RepByUserName need to use PromiseResult as a parameter?  If so, include that in the query.  I don't see anywhere where you supply a UserName to that query.  If you are using the screen property assignment for that, do it async: screen.setRepUserName(PromiseResult).then(function () {//... do your next promise now that the value is done being set}) -- but I would just use PromiseResult right in the query to make the whole thing simpler.

    In general, I have moved away from the msls.PromiseOperation because it can make things more complicated than necessary.  If CallGetUserName is an ajax call, it is already being executed asynchronously (unless you changed the default ajax setting), so there is no need to wrap that in a promiseOperation.  



    • Edited by Hessc Wednesday, December 2, 2015 3:59 AM
    Wednesday, December 2, 2015 3:56 AM
  • Does CallGetUserName contain all of the operation complete code needed to fulfill the promise operation?

    Not really sure what you mean by this. The code is near identical to Michael's code. As I said above, I've set a breakpoint and used Fiddler to ensure that the query completes and is returning valid data. But with everything happening asychronously, I'm not sure how the breakpoint lines up in time with when Fiddler sees the result.

    CallGetUserName is an ajax call that should return a string that is the username. RepByUserName is a query created in the Query Designer and RepUserName is a screen string parameter that is used by RepByUserName. So I was putting PromiseResult into RepUserName by doing:

    screen.RepUserName = PromiseResult;

    What you seem to be suggesting is to nest the asynchronous calls even further:

    msls.promiseOperation(CallGetUserName).then(function PromiseSuccess(PromiseResult) {
        screen.setRepUserName(PromiseResult).then(function () {
            screen.getRepByUserName().then(function (results) {
                var temp = results.data[0].RepFirm.RepFirmID;
            });
        });
    });

    And that doesn't work at all. No inner breakpoints are ever hit.

    What do you mean by, "I would just use PromiseResult right in the query to make the whole thing simpler."? How would I use PromiseResult in a screen query without setting it to a screen parameter?

    Wednesday, December 2, 2015 3:33 PM
  • Kyle, I will get back to you on this in a bit.  I need to look through some of my projects to get you the details.
    Wednesday, December 2, 2015 10:01 PM
  • The client side dynamic create may be the way to go. I haven't tried it yet since I have a bunch of entities that need to be connected to my Quote and I think that all those multiple queries might slow things down too much.

    I've spent the day building my WCF RIA Service. But I can't get it to connect to the server project. There are some nuances that I'm not understanding.

    I can't have full entities in the definition of the class I want to return? I tried to and I forget the exact error message, but it said it wasn't allowed and that they would be excluded. Which defeats the purpose.

    I tried to just have the inserting method in the service but that's not allowed. If I did implement some sort of default query, would I then be able to connect the service and see the inserting method? Probably try this first thing in the morning.

    One of the errors I encountered today said that the query must return either IEnumberable of T or a singleton. I tried to just return the Quote object I had created after I had inserted it but received the error "cannot be marked composable since its return type does not implement IEnumerable of T". A singleton is just a single instance of T that isn't enumerable, right? So what is the proper syntax to just return a singleton instead of a list of entities? 

    For example, this isn't a problem:

    Public Function GetNewDefault() As IQueryable(Of QuoteDefaults)

    But this does not work:

    Public Function GetNewDefault() As Quote

    Thursday, December 3, 2015 12:43 AM
  • Kyle,

    Notwithstanding the issues you are having with RIA Services, on the javascript end, I still think there has to be a problem with your implementation of the promiseOperation.  If the function you are calling (CallGetUserName) does not properly handle the operation.complete(), or something in there is undefined (or any number of other things that could fail silently), you will observe that your queries execute and return values in fiddler, but nothing works in LS (i.e. no breakpoints will be hit, variables will be null, etc.). That is because the LightSwitch runtime is waiting for the promiseOperation to resolve and it never does, even though the queries called inside have successfully executed.  I have experienced this before and it is a pain to debug.  If you post your CallGetUserName code, I may be able to help.  However, as a first step, try removing the promiseOperation completely and simply call your ajax function and then set the screen property value and call the screen query in the ajax "success" handler.  If that works, you will know it was the pesky promiseOperation.  If not, then you need to troubleshoot the ajax function.  Nesting promises is not a problem at all in LightSwitch.  In fact, it works quite well. If you are not hitting internal breakpoints, something is wrong with the promiseOperation.  This is the example promiseOperation from the LS API reference.  Notice how they pass operation as a parameter  and then call operation.complete().  If you are calling an ajax function, you need to pass operation as a parameter and do operation.complete() in the ajax "success" handler.

    var northwind = "http://services.odata.org/Northwind/Northwind.svc"; 
        return msls.promiseOperation(function (operation) { 
            OData.read({ requestUri: northwind + "/Orders?$top=10", 
                recognizeDates: true, 
                enableJsonpCallback: true }, 
                         function success(result) { 
                             var results = result.results; 
                             for (var i = 0, len = results.length; i < len; i++) { 
                                 var importedOrder = screen.Orders.addNew(); 
                                 importedOrder.OrderDate = results[i].OrderDate; 
                             }  
                             operation.complete(); 
                         }, 
                         function error(e) { operation.error(e); }); 
        }); 

    What I meant by "use the PromiseResult right in the query is: 

    myapp.activeDataWorkspace.aris_dbData.RepByUserName(PromiseResult).execute().then(function (results) {
                    var temp = results.results[0].RepFirm.RepFirmID;
                });
    Don't use a screen parameter to set the query parameter -- although if everything in the promiseOperation is working correctly, using a screen parameter should work just as well.

    Thursday, December 3, 2015 4:28 AM
  • As I said, the function is straight out of Michael's book. It looks like this:

    // This function will be wrapped in a Promise object
    function CallGetUserName(operation) {
        $.ajax({
            type: 'post',
            data: {},
            url: '../web/GetUserName.ashx',
            success: operation.code(function AjaxSuccess(AjaxResult) {
                operation.complete(AjaxResult);
            })
        });
    };

    I tried your suggestion of calling the ajax function directly. I definitely need some help understanding the syntax.

    I tried this:

    myapp.BrowseQuotes.created = function (screen) {
        var temp;
    
        $.ajax({
            type: 'post',
            data: {},
            url: '../web/GetUserName.ashx',
            success: myapp.activeDataWorkspace.aris_dbData.RepByUserName(AjaxResult).execute().then(function (results) {
                    var temp = results.results[0].RepFirm.RepFirmID;
                })
        });
    };

    And this:

    myapp.BrowseQuotes.created = function (screen) {
        var temp;
    
        $.ajax({
            type: 'post',
            data: {},
            url: '../web/GetUserName.ashx',
            success: operation.code(function AjaxSuccess(AjaxResult) {
                screen.RepUserName = AjaxResult;
                screen.getRepByUserName().then(function (results) {
                    var index = results.data.length;
                    temp = results.data[0].RepFirm.RepFirmID;
                    screen.FirmNo = results.data[0].RepFirm.RepFirmID;
                });
                operation.complete(AjaxResult);
            })
        });
    };

    In both cases, the GetUserName.ashx is never called.

    But this would make the call:

    myapp.BrowseQuotes.created = function (screen) {
        var temp;
    
        $.ajax({
            type: 'post',
            data: {},
            url: '../web/GetUserName.ashx',
            success: function success(result) {
                temp = result;
                myapp.activeDataWorkspace.aris_dbData.RepByUserName(result).execute().then(function (results) {
                    screen.FirmNo = results.results[0].RepFirm.RepFirmID;
                });
    
            }
        });
    };

    Do you have any resources that explain the proper syntax for the handlers? A cursory search didn't turn up much. But I'll keep digging through the day to see if I can find anything better.

    Now this last one, if I let it run full speed, the screen property FirmNo will not get populated. But if I have a breakpoint on that line, sometime it will populate, sometimes not. Depends on how long I linger when it breaks. But more more often than not, it does not get populated. Even though I'm seeing the result in Fiddler.

    So that doesn't work all that well either. Which, if I read you correctly, leads us to the ajax function itself, also copied from Michael. But I'm not really sure that there's a problem here:

    Public Class GetUserName
        Implements System.Web.IHttpHandler
    
        Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
            Dim serverContext = GetServerContext()
            context.Response.ContentType = "text/plain"
            context.Response.Write(serverContext.Application.User.Name)
        End Sub
    
        ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
            Get
                Return False
            End Get
        End Property
    
    End Class
    I'm sure that ensuring that a query is complete before attempting to use it's results isn't that hard. But I just can't seem to wrap my head around it.

    Thursday, December 3, 2015 4:23 PM
  • Okay most of my questions were answered in the article Walkthrough: Adding Query Methods. So I created a simple default query with a simplified Quote class. Then I tried to write an Insert function that would create, insert, and return a single quote entity.

    When I try to update the data source, I get the error: Entity XXX.Quote has a property 'CustomerReference' with an unsupported type.

    Based on some reading I've done, I think I need to add something like the following that I found here:

    <EnableClientAccess()>  _
    Public Class OrderDomainService
        Inherits LinqToEntitiesDomainService(Of AdventureWorksLT_DataEntities)
    But Visual Studio gives me not defined errors if I try to add that attribute or inherit from that class. And it does not give any potential fixes that might be helpful. So I still have no idea how to return a single entity that contains references to other entities in a WCF RIA Service. Any help would be most appreciated.

    Thursday, December 3, 2015 9:32 PM
  • .. I still have no idea how to return a single entity that contains references to other entities in a WCF RIA Service. Any help would be most appreciated.

    I have no examples.

    However, I have never needed to do that. What I do is:

    1) Create a custom WCFRIA class 

    2) Put all the data I need in that class

    3) Return it to the LightSwitch UI layer

    I wrote the article: LightSwitch Survey: Handling Complex Business Logic Using WCF RIA Services to demonstrate how you can have tables that have related tables, yet make it work just fine with LightSwitch. Download the code example and play with it.


    Unleash the Power - Get the LightSwitch HTML Client / SharePoint book

    http://LightSwitchHelpWebsite.com

    • Proposed as answer by Angie Xu Tuesday, December 15, 2015 5:54 AM
    Thursday, December 3, 2015 9:44 PM
  • Are you doing a post or a get with that ajax call?  It looks like a get, but is marked as a post.  That is something to check for sure.  try replacing type: 'post' with type: 'get'.
    Friday, December 4, 2015 1:50 AM