Accessing Data inside the Middle Tier, with Local Data Adapters

One of the new features we introduced to Data Abstract in the latest release is the Local Data Adapter, or LDA, for short.

What is a Local Data Adapter, and What Does it Do?

If you’ve used Data Abstract before, you are probably familiar with the LDA’s older sibling, the Remote Data Adapter. Data Abstract is built around the concept of multi-tier data access, and the Remote Data Adapter (RDA) forms the central hub on the client side to access data from the remote server, ie from the middle tier. The RDA knows how to request data for one or more tables from the server, how to post changes back, and how to handle reconciliation problems. The RDA understands various client side technologies such as Dynamic Where and DA SQL, and – on .NET – it comes in two versions, one that support regular DataSets, and another one that uses DA LINQ.

Sometimes, however, it is also necessary to work with data locally, within the middle tier. There are several scenarios for this – for example, your business rules code might need to access data to verify or adjust changes submitted from a client, or independent code running in the middle tier server might need to work on data, for example to generate reports, or mine data.

In the past, accessing data within the middle tier has been less straight-forward than it should have been: one either had to work with the lower-level APIs of Data Abstract or use ADO.NET directly, bypassing DA (and the Schemas and everything that goes along with them) altogether. The LDA changes that.

The Local Data Adapter is, essentially, a component that works just like the RDA, except that it is specifically designed to work with data within the same tier. It provides the same APIs for data access as the RDA does (including DA LINQ, on .NET, and DA SQL on both platforms) but does not use RO to talk to a remote server and instead works directly with a local data service.

Setting up an LDA

As mentioned above, the LDA works directly against a local DataService, so it needs none of the setup of RemObjects SDK communication infrastructure that the RDA does. There’s no need for a Channel, a Message or a RemoteService. Instead, you just connect the LDA to a service directly, in one of two ways.

  1. If your code already has a reference to a concrete service instance, that reference can simply be assigned to the LDA’s ServiceInstance property, and be directly usable.

This is especially helpful if you’re using the LDA within event handlers on the Data Service or Business Processors. If your code is already executing in the context of a Data Service, so why not use it?

  1. Alternatively, you can set the ServiceName string property to the name of a data service that exists within your server. As needed, the LDA will obtain a reference for this service using RemObjects SDK’s regular server manager infrastructure. The LDA and RO will take care of instantiating (and releasing) the service as needed, and the regular instantiation mechanics will apply (for example, your chosen class factory will be used).

This option is useful if your code does not have a reference to a service yet, for example when you are accessing data outside of a client request. In a sense, this second option more closely resembles how the RDA works – it connects to a data service by name; it just does not do it across the network, but locally.

One thing to keep when using the LDA is that while it is bypassing the the RO network infrastructure to directly call the local data service as needed, any restrictions implemented in your service will still apply. For example, if your data service requires login or uses Roles to restrict access, it will need a session with the appropriate privileges. When assigning ServiceInstance to self, that session will be inherited from the outer request, but when using ServiceName, you might need to set the SessionID and manually set up a session as expected by your service.

Two Examples

Let’s look at the LDA in action with two examples. The code shown here uses the DA LINQ version of the LDA, in Data Abstract for .NET, but the same principles would apply using DataSets, or using the Delphi version of the LDA.

First, let’s write a BeforeProcessChange event for a Business Processor, to validate and influence changes as they are applied from the client.

To keep the code snippet short, we’ll focus on the update case only. The code first uses the LDA to obtain the original record that is being updated. In the real life project (Bugs 7) that this code is taken from, the “Issues” table is filtered by what individual users are allowed to see, so this lookup not only returns the original row, it also verifies that the user is indeed allowed to access it. Once obtained, we can do arbitrary validation of the data. Finally, we also use the LDA to insert an extra row into an entirely separate “History” table, in this case serving as a sort of log of each changes:

void bpIssues_BeforeProcessChange(...){// instantiate a new LINQ LDA that uses the same Data Service lda =new LinqLocalDataAdapter(); lda.ServiceInstance= self;if(ea.DeltaChange.Type== ChangeType.Update){// get the Primary Key, "ID&" from the Delta... Int64 oldID := ea.DeltaChange.OldValues["ID"]as Int64;   // ... and look up the issue as it is now currentRow =(from i in lDA.GetTable<issues>where i.ID== lOldID).FirstOrDefault;   // if not found, the user probably isn't allowed to touch it!if(currentRow !=null)thrownew Exception(String.Format("No access to original row {0}, or row does not exist.", oldID));   // do some more checks of ea.DeltaChange vs currentRow, if needed.if(currentRow.locked)thrownew Exception("Row has been locked to prevent future changes"));   // ...// once all is approved, insert an entry into a "History" table.var history =new History(); history.IssueID= lOldID; history.UpdatedByID= Session["UserID"]as Int32;// who changed it? history.UpdatedDate= DateTime.Now; lda.InsertRow<History>(lHistory); lda.ApplyChanges();}else...}
So far so good. As a second example, we’ll use the LDA outside of a DataService. In this case (again a trimmed-down real life example from Bugs7), we have a scheduling process that looks at all the rows in the “Issues” table to calculate the appropriate execution order and assign issues to the proper developers. This code runs on a Systems.Timers.Timer, to trigger at regular intervals.
void TimerElapsed(...){using(LinqLocalDataAdapter lda =new LinqLocalDataAdapter()){ lda.ServiceName='Bugs7DataService'; lda.SessionID= Guid.NewGuid;   // create a session and set the roles needed for the tasks at hand ISession s = SessionManager.GlobalSessionManager.GetSession(lda.SessionID); s.Roles=["Internal", "Scheduler", "RealDB"];   // load the data we need, via DA LINQvar issues =from i in fLDA.GetTable<scheduledissues>where(i.StatusID!= Status_Closed) orderby i.CreatedDate;// do our processing on the "Issues" sequence.}}
As you see, we create the new LINQ LDA, and initialize it with the name of our data service (in this case, this is the same service that is also exposed publicly, but it could just as well be a specific service provided only for internal use).

Because our service requires login, we set its SessionID property to a new GUID, and then set up a session with that ID (essentially faking a successful “login”), and also configure a couple of Roles that our service expects; in this case “Internal” tells the service it is being accesses from within the server (i.e. not as a client request), which relaxes some security restrictions, and “RealDB” will prompt it to connect to the production database (rather than a second test database that it also serves).

Summary

This post is only meant to give a brief introduction to the LDA. You can read more it on the Local Data Adapter in our wiki.

Yours,
marc

The [Bugs 7](http://www.bugsapp.com) Server (the site linked here will be filled with more information, soon) is actually written in [Delphi Prism](http://www.remobjects.com/prism), but i’ve converted the code snippets to C# for this blog post.