locked
Entity property shows as undefined when it is present - HTML Client RRS feed

  • Question

  • I'm having an issue with programmatically deleting records in the HTML Client.

    I have a many to many relationship setup by using a mapping table hosted in SQL Server.

    When the user takes a certain action, I need to programmatically remove all the associated mapping records.

    Here is the basic flow of how I set this up:

    When the user takes the certain action, I run a query that returns a collection of all the associated entities. If the collection is not null, I show a message box that forces the user to confirm that this is what they want to do. If the message box result is yes, I for a forEach on the collection to delete the entities.

    Before I delete the entities I need to update counters in both of the mapped entities:

        var mapTableEntityCollection = null;
        myapp.activeDataWorkspace.SQLData.GetMappings(screen.Entity1.Entity1_ID).execute().done(function (EntityCollection) {
            mapTableEntityCollection = EntityCollection.results;
            if (mapTableEntityCollection != null) {
                msls.showMessageBox("Entity2's are mapped to this Entity1.\n Do you want to unmap the Entities?", {
                    title: "Unmap Entities",
                    buttons: msls.MessageBoxButtons.yesNo
                })
                .done(function (result) {
                    if (result === msls.MessageBoxResult.yes) {
                        mapTableEntityCollection.forEach(function (mapTableEntity) {
                            // Delete the entity
                            if (mapTableEntity.details.entityState !== msls.EntityState.deleted && mapTableEntity.details.entityState !== msls.EntityState.discarded) {
                                mapTableEntity.Entity1.CountOfEntity2--;
                                mapTableEntity.Entity2.CountOfEntity1--;
                                mapTableEntity.deleteEntity();
                            };
                        });
                    }
                });
            };
        });
    

    Sometimes the above code works, sometimes it doesn't.

    Sometimes I get a "CountOfEntity2 undefined" error.

    When I run this in debug mode and put a breakpoint at the line of code doing the count decrement, I can see in VS that sometimes the mapTableEntity.Entity1 is undefined, thus the above error. HOWEVER, when I look at the mapTableEntity object in the debugger and expand the '_' tree, it shows that there IS indeed an Entity1 object.

    This is an issue if the root entity shows an object, but referencing that object comes up as undefined.

    Can anyone explain to me what is going on here?

    I would provide the actual code, but this project is massive (with over 175 tables in the on-site SQL database, and about 60 screens). So I fear that like my intelliSense being broken by such a high project, that this issue may be due to the same thing. Or perhaps something to do with the SQL database. But this appears to be a LightSwitch issue.


    -Christopher DeMars

    Thursday, May 2, 2013 3:35 PM

Answers

  • Hi Christopher,

    I'm using Northwind's Customer and Order here for an example.

    By default, when you try to load a collection of Orders, OData Service will not include each Order's Customer in the result. The Order's Customer is a reference property referencing another Entity Object (in another table), and by default they are not loaded as part of an Order's query (think of SELECT * FROM ORDERS, you only get the Customer_ID).

    Therefore, in your code, at that point, all entS2Q entities are loaded, but each entS2Q Quote_Quantity is not loaded.

    • From datajs view - this is a __deferred object.
    • From LightSwitch view - the Quote_Quantity is undefined at that time. But the moment you try to access the property, it knows that it has not been loaded, and will try to load itself. Of course this load is an async operation, so a moment later Quote_Quantity will raise a notification change saying that it has a value now. This behavior is mainly for UI binding benefit. I think that's why sometimes your code work and sometimes not.

    First thing I would try is to use the expand operation to tell OData Service that you want to include the relevant reference properties in the result.

    myapp.activeDataWorkspace.SQLData
        .GetMappings(screen.Entity1.Entity1_ID)
        .expand("Entity1")
        .expand("Entity2")
        .execute()
        .done(function (EntityCollection) {
            mapTableEntityCollection = EntityCollection.results;

    The code above is written to match your sample code. In your application I believe that would be "Quote_Quantity" and "Quote_Subtrate" instead of "Entity1" and "Entity2".

    Best regards,
    Huy


    Thursday, May 2, 2013 7:49 PM

All replies

  • Here is an actual screenshot:

    Let me know if I am mistaken about the entities being ===


    -Christopher DeMars

    Thursday, May 2, 2013 3:56 PM
  • Hi Christopher,

    Would you please provide me some more information:

    - When expanding the object in "Debug Screen, Expanded Details" above, can you expand Quote_Quantity (the one you highlighted in red) and show me the object?

    - At the same point, can you type the following in the Watch Window and let me know the value of them?
      + entS2Q.details.__Quote_Quantity
      + entS2Q.details.__Quote_Quantity.state
      + entS2Q.details.__Quote_Quantity.value

    Thanks,
    Huy

    Thursday, May 2, 2013 5:54 PM

  • -Christopher DeMars

    Thursday, May 2, 2013 7:04 PM
  • This code is executed in the .then() method of a promise object for the query, so why would the entity be deferred? I obviously don't know what deferred means...


    -Christopher DeMars

    Thursday, May 2, 2013 7:22 PM
  • Hi Christopher,

    I'm using Northwind's Customer and Order here for an example.

    By default, when you try to load a collection of Orders, OData Service will not include each Order's Customer in the result. The Order's Customer is a reference property referencing another Entity Object (in another table), and by default they are not loaded as part of an Order's query (think of SELECT * FROM ORDERS, you only get the Customer_ID).

    Therefore, in your code, at that point, all entS2Q entities are loaded, but each entS2Q Quote_Quantity is not loaded.

    • From datajs view - this is a __deferred object.
    • From LightSwitch view - the Quote_Quantity is undefined at that time. But the moment you try to access the property, it knows that it has not been loaded, and will try to load itself. Of course this load is an async operation, so a moment later Quote_Quantity will raise a notification change saying that it has a value now. This behavior is mainly for UI binding benefit. I think that's why sometimes your code work and sometimes not.

    First thing I would try is to use the expand operation to tell OData Service that you want to include the relevant reference properties in the result.

    myapp.activeDataWorkspace.SQLData
        .GetMappings(screen.Entity1.Entity1_ID)
        .expand("Entity1")
        .expand("Entity2")
        .execute()
        .done(function (EntityCollection) {
            mapTableEntityCollection = EntityCollection.results;

    The code above is written to match your sample code. In your application I believe that would be "Quote_Quantity" and "Quote_Subtrate" instead of "Entity1" and "Entity2".

    Best regards,
    Huy


    Thursday, May 2, 2013 7:49 PM
  • First thing I would try is to use the expand operation to tell OData Service that you want to include the relevant reference properties in the result.

    myapp.activeDataWorkspace.SQLData
        .GetMappings(screen.Entity1.Entity1_ID)
        .expand("Entity1")
        .expand("Entity2")
        .execute()
        .done(function (EntityCollection) {
            mapTableEntityCollection = EntityCollection.results;

    The code above is written to match your sample code. In your application I believe that would be "Quote_Quantity" and "Quote_Subtrate" instead of "Entity1" and "Entity2".

    Best regards,
    Huy


    Huy, can you tell us why you use ".done" and not ".then" to get the results of the Promise object?

    Thanks


    The Visual Studio LightSwitch Marketplace

    http://LightSwitchHelpWebsite.com

    Thursday, May 2, 2013 8:20 PM
  • Michael,

    He probably just took it from my code.

    Without getting into a long winded description of the differences, the primary difference is the return type.

    The .then() method returns a promise, while the .done() method does not. This effectively means that you can nest multiple .then() methods, but you can have only 1 .done() method.

    It also directly affects how exceptions are handled by each method:

    • The .then() method, being a promise object, passes an exception back through an error handler function you specify. If you choose to not implement a handler function, the exception may or may not be passed back to the calling method (depending on the exception type). Although there are ways to make sure any unhandled exceptions get passed back, like adding return; at the end of the success function.
    • The .done() method will always throw exceptions back to the calling method.

    So I chose to use .done() because of the way it handles exceptions.

    I hope that helps a little. If you want, I can explain it in more detail.


    -Christopher DeMars

    Thursday, May 2, 2013 9:15 PM
  • Thursday, May 2, 2013 9:18 PM
  • Hi Michael,

    Yes, it's my copy and paste. But you can see here for a pretty detailed explanation with sample code.

    Best regards,
    Huy

    Thursday, May 2, 2013 9:19 PM