Quantcast
Channel: Category Name
Viewing all articles
Browse latest Browse all 10804

Saving to Multiple Data Sources in the HTML Client (Stephen Provine)

$
0
0

One of the great features of Visual Studio LightSwitch and Cloud Business Apps (CBA) is its ability to perform what we call data mashup. You can connect to multiple data sources, configure virtual relationships between entities in different data sources, and then visualize all of the data at the same time.

One interesting data mashup scenario is to attach to an external entity, then extend that entity with some application-specific intrinsic (internal) data. Perhaps the external entity represents basic Employee information but the application is a corporate Facebook-like application which would like to additionally store more detailed profile information such as a photo and areas of expertise.

In this scenario, it is possible that application users would want to view and edit all the employee information together. LightSwitch easily supports mashing up the external and intrinsic entities into a single view, but editing is problematic, as changing data on both the external and intrinsic entities at the same time results in pending changes to both data sources, and these changes cannot be saved in a transactional manner.

Because of this problem, LightSwitch does not automatically enable saving to multiple data sources. However, there is a code entry point through which an application developer can write logic that chooses precisely how to handle such a scenario. This post walks through an example of how to effectively make use of this entry point when the situation calls for it.

Example

The example application we are going to create takes an existing data source that contains some basic person information, then extends it with additional profile information stored in the intrinsic application database. We will then build a HTML client that can both view and edit all of the person information in a single set of browse, view and edit screens.

This post assumes some previous knowledge of LightSwitch, including how to create LightSwitch projects, attach to existing data, navigate the entity designer and create screens using the built-in templates. Also, this example assumes that you are using Visual Studio 2013 Update 1 along with the March 2014 Update of the Office Developer Tools (which includes LightSwitch).

Creating the Project and Attaching to Person Data

Start by creating a new LightSwitch HTML application called “ProfileApplication”. After the project has been created, we want to attach to the existing person data source. For this post, let’s use an example read/write OData service located at http://www.odata.org/odata-services. Open this link, choose the “OData v3” tab and click on the “OData (read/write)” link. This will navigate you to a temporary URL assigned by the OData web site that allows for both reading and writing data. Copy this URL to the clipboard – it will look something like “http://services.odata.org/V3/(S(code))/OData/OData.svc/”.

Now in your LightSwitch project, attach to this OData service, making sure to uncheck the “Attach to this data source as read-only” option and picking “None” as the authentication type. Choose to include the “Person” entity. After you click “Finish”, ignore the warnings.

Defining Person Profile Data

To extend the existing person data with additional profile information, add a new table and name the entity “PersonProfile”. We want to relate instances of this entity to the existing Person entity, so let’s configure a virtual relationship between them as follows:

image

Let’s also configure a second virtual relationship that connects a person with their manager. First add a property to the person profile entity called “Manager_ID” of type Integer that is not required, then configure a new virtual relationship as follows:

image

Now let’s add some profile specific properties to the entity. The person profile entity should now look something like this:

image

Finally, let’s do a little customization of the defaults in preparation for generating some screens. Switch the entity designer to the HTMLClient perspective, and for each of the “Person”, “Person_ID” and “Manager_ID” properties, uncheck the “Display by Default” option in the properties window. Also, open the Person entity from the attached data source and make the same change to not display the “ID” property by default.

Creating Screens

With our data model in place, we’re now ready to create some screens over the data. In the HTML Client project, open the add new screen dialog and choose the “Common Screen Set” template. Pick “DemoServiceData.Persons” as the screen data and hit OK.

Let’s do one quick customization of the view and edit screens to add the Manager property from the person profile entity. This was not added by the common screen set template because it navigates more than one level deep in the entity graph. In the view screen, add a new item under “left” using the “Other Screen Data…” option, and enter “PersonProfile.Manager” as the screen data path. Then move the newly created item so it is between Department and Skills. Now in the edit screen, add a new item, also under “left”, configured in the same manner.

image

Now hit F5 to run the application and see what we have. You are first presented with the browse screen over the persons on the external data source:

image

Clicking on a person opens the view screen, which includes not only the “Name” property from the external data source but also all the person profile properties:

image

These profile properties are currently empty, of course, as we haven’t added any data yet. You also see “Created By”, “Created”, “Modified By” and “Modified” properties which are maybe confusing since these properties apply to the profile information entity only. Feel free to rename these properties to something that is clearer.

Click the Edit button and you’ll see the add/edit screen, but notice that you can only make changes to the name of the person:

image

All the other properties are read only because a person profile entity has not yet been created and associated with the person. To provide a seamless experience for the end user, we want to make sure that person profiles are created on demand when they are needed. In this case, that would be when the add/edit screen for a person is opened.

Let’s add this logic to the add/edit screen. Close the running application and return to the designer for the add/edit screen. Using the “Write Code” link in the designer’s navigation bar, choose to write code for the “created” event. Add the following code:

myapp.AddEditPerson.created = function (screen) {if (typeof screen.Person.ID !== "number") {
        myapp.activeDataWorkspace.DemoServiceData.Persons
            .orderBy("ID").execute().then(function (persons) {var lastPerson = persons.results[persons.results.length - 1];
                screen.Person.ID = lastPerson.ID + 1;
            });
    }
    screen.Person.getPersonProfile().then(function (profile) {if (!profile) {var profile = new myapp.PersonProfile();
            profile.Person = screen.Person;
        }
    });
};

The first part of this code handles a deficiency in the sample OData service we are using, which is that it does not offer auto-increment ID values. This code is not related to the purpose of this post so we won’t focus on it. The second part of the code gets the profile associated with the person entity, and if the profile is missing, it creates a new one and associates it to the person entity.

Run the application again and you will see that the add/edit screen is now fully editable. However, it is not fully functional. If you simply click to save or you only modify profile properties, it succeeds because only the intrinsic database needs to be updated. But if you try to change both the name and some profile properties at the same time, you will get the error “Unable to save changes to multiple data sources”. Let’s see what we can do to fix this.

Saving to Multiple Data Sources

We are finally ready to illustrate the feature highlighted by this article! In Visual Studio, locate the code file where you wrote your add/edit screen created code. Underneath this code, add the following:

myapp.onsavechanges = function (e) {var personData = myapp.activeDataWorkspace.DemoServiceData;
    e.detail.promise = personData.saveChanges()
        .then(function () {var profileData = myapp.activeDataWorkspace.ApplicationData;return profileData.saveChanges();
        });
};

Let’s talk about what this code is doing. First of all, it’s handling a global event, and there can be only one handler. We added it to the code behind the add/edit screen in this example because it is the one place in the application where save occurs across multiple data sources. In a real application, you likely want to add this code in a more global location, perhaps in the code behind your home screen. And just to be clear, this code will work globally (as in across all screens), since the saveChanges method does no work if there are no changes to the data source in question.

Second, this code institutes a particular semantic around how the save occurs. First the external data source is saved, then the intrinsic database is saved, but only if the external data source was saved successfully. All this is done through promise objects, where the value of e.detail.promise is set to the final promise representing both save operations. By providing the promise back to the runtime, LightSwitch can make sure to wait until both data sources are saved before giving control back to the user.

Now if you run the application, you will find that when you edit a person, the application will first ensure a profile is created and then allow updating both the name and the profile information at the same time.

Handling Transaction Inconsistencies

Let’s consider what this code implies in terms of transactional consistency (or more specifically, lack of it). Notice that data could be saved to the external data source successfully, but it could fail to save profile information to the intrinsic database. To prove this is the case, let’s add a server-side validation rule on the profile information that causes the save to fail.

In Visual Studio, right click the “ApplicationData” node in your Server project and choose “View Code”. In this code file, add the following partial method:

partial void PersonProfiles_CanUpdate(ref bool result)
{
    result = false;
}

This effectively disables updating any profile information (although it can still be created). Now run the application again. Choose a person and try to edit this person’s name and profile information at the same time. If a profile hasn’t been created yet, the save will succeed, but if the profile was already created and is being updated, the name update will be saved but the application will return the error from the second save operation, like “The current user does not have permission to update entities in the EntitySet ‘PersonProfiles’”.

This is a known and valid behavior of the application as we designed it, but the error message is not particularly informative. We can offer something better by changing our save handler to return an extended error message:

myapp.onsavechanges = function (e) {var personData = myapp.activeDataWorkspace.DemoServiceData;
    e.detail.promise = personData.saveChanges()
        .then(function () {var profileData = myapp.activeDataWorkspace.ApplicationData;return profileData.saveChanges().then(null, function (errors) {
                errors.unshift({
                    message: "The person name was saved, but there was one " +"or more errors saving the person profile information."});throw errors;
            });
        });
};

Now when you run the application and attempt the same update, you get a more informative error message:

image

Wrapping Up

This post illustrated an advanced feature of LightSwitch and Cloud Business Applications that handles saving data to multiple data sources when using the standard save gesture in the application UI. It showed how to handle the myapp.onsavechanges event to implement the necessary semantics for appropriately saving, and then how to provide an appropriate experience for the application user in cases where changes were not fully saved to all the data sources.

We hope this post helps to stimulate some interesting application scenarios, and as always, feel free to leave comments or questions on our forums. Until next time, happy coding!

Stephen Provine
Software Design Engineer
LightSwitch and Cloud Business Applications


Viewing all articles
Browse latest Browse all 10804

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>