locked
A question about how LightSwitch requests a RIA data source RRS feed

  • Question

  • Dears,

    We recently converted a LightSwitch application to become fully based on RIA, we did this to improve performance, and it did! Now we have good control over how the database is queried.

    However, one thing did not improve after the conversion to RIA: the number of HTTP requests sent to the application server.

    When the client loads a grid of a certain entity that had several navigation properties, the client used to get the main entities together with all their related entities in one request and everything appears together.

    For example loading a table of Employees, where each employee has a property Department of type Department, LightSwitch used to get the employees and their departments in one HTTP request, and I even used to be able to control that behavior using the 'Manage Included Data' option in the screen query.

    Now that the screen is based on a RIA query, the option 'Manage Included Data' is grayed out, and LightSwitch is sending one HTTP request to populate the grid of EmployeeDTOes, and then for each EmployeeDTO row retrieved there is another HTTP request to fetch the DepartmentDTO. You can visibly see the grid loaded first with an empty Department column and then the departments get populated gradually as the series of HTTP requests are executed.

    Here is my question: Is there a way to get LightSwitch to query the RIA data source in the same way it queries the intrinsic data source?

    The solution I currently have in mind is to introduce a 3rd property in EmployeeDTO for every navigation property, like this:

    public class EmployeeDTO {
    
    // ...
    
    public int DepartmentId { get; set; }
    
    [Association("Employee_Department", "DepartmentId", "Id", true)]
    public DepartmentDTO Department { get; set; }
    
    [Display(Name = "Department")]
    public string DepartmentDisplay { get; set; } // 3rd property
    
    }

    The third property of type string will contain the display name of the department and is retrieved with the first HTTP request. When creating a grid of Employees, I show the DepartmentDisplay property rather then the Department property.

    But this solution feels really awkward, and is no good when you want to create and editable grid or when you are displaying other properties of your navigation properties or their navigation properties. I want to make sure before I implement this patchy solution that there is really no way to get the same request-behavior in RIA as with the intrinsic data source , can the experts on this forum please provide some guidance?

    This link suggests there really isn't a way: https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/3092582-improve-the-request-model-in-lightswitch

    Best wishes,

    Monday, December 21, 2015 10:35 PM

All replies

  • You have it correct' I do not know of any way to include all those related entities in the same query when using RIA domain service. To populate grids, I always use simple "Digest" entities that only contain enough flattened properties to display all the columns and contain the PK - say EmployeeDigest in your example. In my case Digest entities are always read-only but it would not be hard to make them writable as well when required. The main concept is that I do not add extra properties like you have done to the main entities, but rather create the separate Digest versions as required. Hope this helps.

    Regards, Xander. My Blog


    • Edited by novascape Tuesday, December 22, 2015 8:21 AM
    Tuesday, December 22, 2015 8:18 AM
  • Thanks for your reply.

    Suppose you wanted to create an editable grid of EmployeeDTOes, where the user is able edit 'Department' and several other navigation properties. The flattened class approach won't work and we are stuck with having to send hundreds of HTTP requests.

    This downside of using RIA is quite unfortunate :(

    Tuesday, December 22, 2015 10:06 AM
  • See:

    LightSwitch Survey: Handling Complex Business Logic Using WCF RIA Services

    I get data from multiple tables in one call:

                // Get all the Questions For User
                var colQuestionsForUser = from Survey_Question in this.Context.SurveyQuestions
                                          orderby Survey_Question.Survey.Id
                                          orderby Survey_Question.Id
                                          // Shape the results into the 
                                          // QuestionsForUser class
                                          select new QuestionsForUser
                                          {
                                              QuestionId = Survey_Question.Id,
                                              UserName = strCurrentUserName,
                                              SurveyId = Survey_Question.SurveyQuestion_Survey,
                                              Question = Survey_Question.Question,
                                              isAnswered = ((Survey_Question.SurveyAnswers
                                              .Where(x => x.UserName == strCurrentUserName
                                                  && x.SurveyQuestion.Id == Survey_Question.Id).Count()) > 0),
                                              isActive = false
                                          };

    and

     colQuestionDetailsForUser = (from Survey_Question in this.Context.SurveyQuestions
                                             where objUserQuestionIds.Contains(Survey_Question.Id)
                                             select new QuestionDetailForUser
                                             {
                                                 QuestionId = Survey_Question.Id,
                                                 SurveyId = Survey_Question.Survey.Id,
                                                 UserName = strCurrentUserName,
                                                 Question = Survey_Question.Question,
                                                 Choice1 = Survey_Question.Choice1,
                                                 Choice2 = Survey_Question.Choice2,
                                                 Choice3 = Survey_Question.Choice3,
                                                 Choice4 = Survey_Question.Choice4,
                                                 SelectedChoice = ((from SurveyAnswers in Survey_Question.SurveyAnswers
                                                                    where SurveyAnswers.SurveyQuestion.Id == Survey_Question.Id
                                                                    where SurveyAnswers.UserName == strCurrentUserName
                                                                    select SurveyAnswers).FirstOrDefault() != null)
                                                                    ?
                                                                    (from SurveyAnswers in Survey_Question.SurveyAnswers
                                                                     where SurveyAnswers.SurveyQuestion.Id == Survey_Question.Id
                                                                     where SurveyAnswers.UserName == strCurrentUserName
                                                                     select SurveyAnswers).FirstOrDefault().Choice
                                                                    : intNull,
                                                 Comments = ((from SurveyAnswers in Survey_Question.SurveyAnswers
                                                              where SurveyAnswers.SurveyQuestion.Id == Survey_Question.Id
                                                              where SurveyAnswers.UserName == strCurrentUserName
                                                              select SurveyAnswers).FirstOrDefault() != null)
                                                                    ?
                                                                    (from SurveyAnswers in Survey_Question.SurveyAnswers
                                                                     where SurveyAnswers.SurveyQuestion.Id == Survey_Question.Id
                                                                     where SurveyAnswers.UserName == strCurrentUserName
                                                                     select SurveyAnswers).FirstOrDefault().Comment
                                                                    : ""
                                             });


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

    http://LightSwitchHelpWebsite.com



    Tuesday, December 22, 2015 5:15 PM
  • @Michael - as soon as you have a relationship set up between two RIA entities and you display a properties from both entities in the same grid/table, LS will make one call for the main list of entities and then one additional call per grid/table row to load the related entity to display the call from the related entity.

    On the surface it looks like only one call is made, but if you check the network traffic underneath, you will see that behavior. This is different to the way the native LS database data source works.

    @Ahmad - the next problem you will run into is when you try and refresh an entity with related entities on a view or edit screen. You will find the screen.Employee.details.refresh() throws a server error and you have to use screen.Employee.details.refresh([]) instead, but that only refreshes the Employee entity and not the related entities, you have to manually refresh those by yourself.

    I have the following generic helper function to do that:

    // Generic function to refresh a RIA entity navigation property
    // Params:-
    //    entity : the LS entity
    //    propertyName : name of the navigation property
    //    fkName : name of underlying FK property on entity
    //    pkName : name of PK on related entity
    // Returns a WinJS.Promise
    // Example usage:
    //    screen.Order.details.refresh([]).then(function () {
    //        // refresh navigation property once entity refreshed (call for each navigation property needing refresh)
    //        refreshNavigationProperty(screen.Order, "OrderStatus", "StatusId", "Id");
    //    });
    refreshNavigationProperty = function(entity, propertyName, fkName, pkName) {
        entity.details["__" + propertyName].query._filter = pkName + " eq " + entity.details._[fkName];
        return entity.details["__" + propertyName].query.execute();
    };
        


    Regards, Xander. My Blog

    Tuesday, December 22, 2015 10:06 PM
  • @Michael - as soon as you have a relationship set up between two RIA entities and you display a properties from both entities in the same grid/table, LS will make one call for the main list of entities and then one additional call per grid/table row to load the related entity to display the call from the related entity.

    On the surface it looks like only one call is made, but if you check the network traffic underneath, you will see that behavior. This is different to the way the native LS database data source works.



    Regards, Xander. My Blog


    I do not set up a relationship between two entities, I always return one entity. My sample code is to demonstrate how you return data from multiple tables using a single entity.

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

    http://LightSwitchHelpWebsite.com

    Tuesday, December 22, 2015 10:32 PM
  • I do not set up a relationship between two entities, I always return one entity. My sample code is to demonstrate how you return data from multiple tables using a single entity.
    Ok understood, thanks. That is what I basically do with my Digest concept as well.

    Regards, Xander. My Blog

    Tuesday, December 22, 2015 10:36 PM
  • @Michael

    As long as you are retrieving read-only entities, the 'digest' pattern suggested by novascape (which is similar to your pattern) would do just fine. Once you want to set up an editable grid screen, you have to return the actual navigation properties to allow the user to edit them, and with a grid page size of 45, and say 5 navigation properties per row, you have 1+(45x5) = 226 HTTP requests for a single grid, which is just sad.

    @novascape

    Thanks for sharing your helper function, I haven't come across this issue yet, but now I won't be spending 9 hours fixing it the day I encounter it.

    The somewhat related bug that I recently did spend 9h trying to fix happened because I had the entity class EmployeeDTO implement the interface INotifyPropertyChanged to implement some functionality I won't go into. This caused the EmployeeDTO details screen to show all navigation properties as blank, this is because they were no longer retrieved together with the EmploeyeDTO, but in subsequent HTTP requests, and since the UI was not notified when the HTTP requests completed, it remained in its first state of showing empty navigation properties. That problem did not show itself before in the details screen of the intrinsic Employee type, because all navigation properties were brought in one HTTP together with their parent class.


    • Edited by Ahmad Akra Wednesday, December 23, 2015 2:24 PM
    Wednesday, December 23, 2015 2:23 PM
  • @Michael

    As long as you are retrieving read-only entities, the 'digest' pattern suggested by novascape (which is similar to your pattern) would do just fine. Once you want to set up an editable grid screen, you have to return the actual navigation properties to allow the user to edit them, and with a grid page size of 45, and say 5 navigation properties per row, you have 1+(45x5) = 226 HTTP requests for a single grid, which is just sad.



    You can update the "digest" entity without the need to expose a navigation property.

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

    http://LightSwitchHelpWebsite.com

    Wednesday, December 23, 2015 11:24 PM