Windows DevCenter    
 Published on Windows DevCenter (http://www.windowsdevcenter.com/)
 See this if you're having trouble printing code examples


Object-Oriented Data Programming: C# Meets Caché

by Jesse Liberty
03/28/2006

Virtually every meaningful commercial ASP.NET or Windows application stores and retrieves data. Most programmers use a relational database for data storage, and Microsoft has gone to great lengths to create a set of classes that mediate between the object-oriented viewpoint of C# and VB 2005 programming on the one hand, and the tables/rows/columns perspective of relational databases on the other (ADO.NET).

There are, however, other ways to store your objects, one of which is to use an object-oriented database. The idea would be to create objects, and then just store them and retrieve them without thinking at all about tables/rows/columns or even relations!

MUMPS

Now, with that in the back of your mind, consider this problem: many hospitals and other institutions have been using a non-relational database called MUMPS (Massachusetts General Hospital Utility Multi-Purpose System) for the past 40 years (!). They are deeply invested in this technology, and, hey! it works. In 1994 InterSystems bought up various versions of MUMPS and in 1997 they released Caché, using the MUMPS storage engine and language, but with object-oriented services on top. They realized that these institutions do not want to walk away from their data, but they do want to use modern languages, such as Java and C# to create the front end to their new applications.

MUMPS and C#

Flash forward to today. I was asked by the Dana-Farber Cancer Institute (Partners Health Care) in the Boston area to help a group of in-house developers create a new system for their pharmacy that will use MUMPS Globals (the underlying storage) but with a .NET 2005 Windows Forms front end.

Originally I thought, "OK, I'll build the front end, and someone else will worry about this weird non-relational (InterSystems calls it "post-relational") database. But it turns out that you can create objects in Caché (and let it write the globals automagically), and then use their wizard to import Caché objects into (proxy) C# classes.

This was too good not to write about, because it illustrates so many interesting ideas:

Actually, there are many other nifty lessons to be learned along the way, but the best way to see them is to create an application. We're going to create a very simple application, and walk through all the steps, which include:

Building a Demo Application

To illustrate how this all works we want to build a very simple application that lets you pick from a list of patients and see what medications they take, in what dosage, and by what route (by mouth, IV, etc.).

To implement this, let's think about what objects we'll need, first without regard to implementation details.

Clearly we need a Patient class, and we can imagine that the Patient class inherits from Person, with Person providing the name, address, phone numbers, etc. and Patient providing the patient ID, list of prescriptions, list of doctors, list of disorders, etc.

Let's think about Prescriptions. To start, there is a Medication (e.g., Lexapro), a Dosage (e.g., 20mg), and a Route (e.g., by mouth). There is also a prescribing doctor, a prescription date, the date the prescription was dispensed, how many were ordered, how many were actually delivered, and so forth.

How do we handle the Medication? Many medicines come only in certain dosages, and some are safe only via certain routes (never give yourself an intravenous (IV) epinephrine injection). Ahhh, complexity. For the purpose of this demo, we'll simplify, and create the following truncated classes:

Table 1. Classes
Class Parent Members Type
Person   Name String
    Phone number String
Patient Person Prescription Prescription
    Doctor String
Prescription   Medication Medication
    Dosage String
    Route Route
    DispensingInfo String
Medication   MedicationName String
Route   RouteName String

Of course, in a real application, there would be many more fields, and some of the simple fields here would be more complex (for example, the patient's doctor would be of type Physician which in turn would derive from Person).

Notice that the Medication and Route classes contain only a single member. These classes will be used as lookups, so that we can fill drop-down boxes with legitimate values.

To create even this simple application, we'll need the following forms:

Learning C# 2005

Related Reading

Learning C# 2005
Get Started with C# 2.0 and .NET Programming
By Jesse Liberty, Brian MacDonald

Create the Classes First in Caché

Now that we (more or less) know what we want to create in C#, we start by creating classes in Caché.

To follow along you'll have to download both Caché 5.1 and the Caché Managed Provider for .NET (both are free for developers). You'll also need Visual Studio 2005 or C# Express 2005.

Right-click the Caché Cube (Figure 1) and open the Caché Studio (Figure 2).

Cache Cube
Figure 1. Right-click the Caché Cube ...

open studio
Figure 2. Then open the Caché Studio.

Click on File->New Project to create a new project. Then, so that you can name it, click on File->Save Project. Next, right-click on Classes and click Create New Class to add the Route class as shown in Figure 3.

new class
Figure 3. Creating a new class

When prompted, name the new class Route, and set its type to Persistent, as shown in Figure 4.

class type
Figure 4. Setting the class type

The most important buttons you'll use in the studio are the buttons for creating a New Property and for creating a Query, as shown highlighted in Figure 5.

buttons
Figure 5. The handiest: New Property and Query buttons

We decided earlier that Route would have RouteName, so click on the New Property button and give the class a property RouteName. You are next asked what the property is "for" and in this case it is a single value of type String so you can accept the defaults and click Finish. The Caché class is updated.

Create a Query for Each Class

For each class we'll add a query that we can use to find every object in the class. Click on the fourth button from the left (as shown highlighted in Figure 5) to create a new query and name it FindAllRoutes. Click on Next twice and then use the buttons to copy both %ID (generated by Caché) and RouteName to the right-hand box. Click Finish, and a query is generated for the class (which will become a method in C#).

Query FindAllRoutes() As %SQLQuery(CONTAINID = 1)
{
   SELECT %ID,RouteName FROM Route
}

Continue Adding Classes

To add your second (and subsequent) classes, click on the + sign next to Classes in the Workspace window to expand it, then expand User (to see the Route class). Right-click on User and choose Create New Class as shown in Figure 6.

create new class
Figure 6. Creating another new class

Create the Medication class and give it a string property MedicationName.

Next, create the Prescription class, and add the two string properties Dosage and DispensingInfo. Then add a third property, Route, but when you click Next you'll choose "Relationship" rather than "A single value or type." In C# the Prescription class will have a single object of type Route, but in Caché we'll create a one:one relation between instances of Prescription and instances of Route. On the next page of the wizard, click the radio button indicating that we are creating a one:one relationship, browse to the User.Route object, and name the corresponding property not by choosing an existing property in Route, but by typing in a name for the relationship: PrescriptionToRoute as indicated in Figure 7.

Relationship Wizard
Figure 7. Choosing a one:one relationship

Accept the two selected choices in the next page of the wizard and click Finish. The Prescription class adds a Relationship:

Relationship Route As User.Route [ Cardinality = one, Inverse = PrescriptionToRoute ];

Next we want to create the Person class, and give that class two string properties: Name and PhoneNumber.

Finally, we are ready to create the Patient class. Right-click on User and choose Create New Class, and name the class Patient, but click Next to go to the second step in the wizard. Until now we have been (implicitly) accepting the default Persistent type, which allows the class to be stored in the database. This time, however, we are creating Patient and a sub-class of Person, so click Extends and use the Browse button to find the super class (Person) as shown in Figure 8.

Create Sub Class
Figure 8. Selecting the super class

When you click OK in the Class Selector Dialog, the class name User.Person will be filled in as the name of the super class; click Finish.

Add Doctor as a string property, then add Prescription. This time, when you choose Relationship, be sure to choose "Many:many other objects" from the relationship characteristics, as shown in Figure 9.

One To Many
Figure 9. Choosing a one:many relationship

By creating this one:many relationship in Caché we ensure that in C#, the Patient object will have a collection of Prescriptions.

That's it! We've created all the Caché classes we need. Click the Rebuild All button, as shown in Figure 10, to save and build the application in Caché.

Rebuild All
Figure 10. Rebuild All saves and builds the app in Caché

Assuming the result window says that your "Compilation finished successfully," you are ready to leave Caché and move to Visual Studio.

Create a Windows C# project named OreillyCacheDemo. You'll need to install the Caché Object Binding Wizard into your Tools list as explained in the .NET documentation you downloaded for Caché. Run that tool and connect to your database. Choose Source Files, C# and check the Include Subclasses checkbox. To set the Output File name, use the navigation button (small button with ellipsis, indicated with a red arrow in Figure 11) to navigate to your Visual Studio project and name the file CacheClasses.cs. Click the + mark next to User and select every class except Patient (which will be included because you chose Include Subclasses), then click Generate, as shown in Figure 11.

Thumbnail, click for full-size image.
Figure 11. Generating a proxy class--click for full-size image.

Clicking Generate creates the proxy class named CacheClasses.cs in your project. Right-click on your project and choose Add Existing Item to add this proxy class to your project.

Let's start by creating some Routes and Medications so that when you create your Patient you can create Prescriptions that use these objects. To get started, add a menu with the following choices: Create Routes, Create Medications, and Create Patients, as shown in Figure 12.

Adding Menu
Figure 12. Adding a menu

(I've squeezed down my work environment to fit the prescribed width for O'Reilly images.)

Before you can begin, there are a few things you must do. First, you need a reference to the InterSystems.Data.CacheClient.dll that you'll find in the Bin directory of the DotNet directory you downloaded from InterSystems. Second, every form wants to include two using statements:

using InterSystems.Data.CacheClient;
using InterSystems.Data.CacheTypes;

Create a member variable CacheConnection and initialize it to establish the connection to the Caché database:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using InterSystems.Data.CacheClient;
using InterSystems.Data.CacheTypes;

namespace OreillyCacheDemo
{
    public partial class CacheDemo : Form
    {
        private CacheConnection CacheConnection;

        public CacheDemo()
        {
            InitializeComponent();
            CreateCacheConnection();

        }

        private void CreateCacheConnection()
        {
            try
            {
                CacheConnection = new CacheConnection();
                CacheConnection.ConnectionString = 
                    "Server = localhost;"
                   + " Port = 1972;"
                   + " Namespace = USER;"
                   + " Password = SYS;"
                   + " User ID = _system;";
                CacheConnection.Open();
            }
            catch (Exception CacheConnException)
            {
                MessageBox.Show(
                    "Create Cache Connection failed " + 
                    CacheConnException.Message);
            }
        }

For all subsequent forms, you'll pass the existing CacheConnection object into the form constructor, rather than reconnecting.

Create a second form, Add Routes, and add the controls shown in Figure 13:

Add Route Form
Figure 13. Creating the Add Routes form

Return to the CacheDemo page and double-click on the Create Routes menu entry to create the event handler (createRoutesToolStripMenuItem_Click) and add the following code:

AddRoutes dlg = new AddRoutes(CacheConnection);
dlg.ShowDialog();

Notice that you are passing in the CacheConnection object to the constructor. Open the code for the AddRoutes form and modify the constructor as follows:

public partial class AddRoutes : Form
{
    private CacheConnection CacheConnection;
    private DataTable tblRoutes;

    public AddRoutes(CacheConnection CacheConnection)
    {
        InitializeComponent();
        this.CacheConnection = CacheConnection;
        FillRoutes();
    }

Visual Studio will offer to create a stub for FillRoutes, which you will fill with the following code:

private void FillRoutes()
{
    CacheCommand CacheCommand = User.Route.FindAllRoutes(CacheConnection);
    tblRoutes = new DataTable();
    tblRoutes.Load(CacheCommand.ExecuteReader());

    lbRoutes.DataSource = tblRoutes;
    lbRoutes.DisplayMember = "RouteName";
    lbRoutes.ValueMember = "ID";
}

Notice that you create a CacheCommand object (which Intellisense will recognize, along with all the other Caché objects, thanks both to your using statement and to the reference you added earlier). This CacheCommand object is filled with the return value you receive when you call the member method FindAllRoutes. This method was created from the query you added to the class.

At this point a terrible chill passes over you. You forgot to add queries to the other classes, didn't you? No problem. Go back to Caché Studio and add queries to each class, then rebuild the project, delete the CacheClasses.cs file from your Visual Studio project, regenerate the proxy and add it back to the Visual Studio project, and you're all set!

Your next step (continuing with the AddRoutes dialog), is to implement event handlers for the Add button. The goal of this event handler is to create a new Route object, whose RouteName property will be taken from the text box, and to persist it.

You will set each property of the Route object (in this case there is only one). Intellisense will help, but if you want to remind yourself of what properties an object has, you need only return to Caché Studio, click on the class, and examine the names of the properties.

Finally, you'll call your C# method FillRoutes to refill the listbox with the new Route object added.

private void btnAdd_Click(object sender, EventArgs e)
{
    if (txtRoute.Text.Length < 1)
        return;

    User.Route newRoute = new User.Route(CacheConnection);
    newRoute.RouteName = txtRoute.Text;
    CacheStatus CacheStatus = newRoute.Save();
    if (CacheStatus.IsOK == true)
    {
        txtRoute.Text = string.Empty;
        FillRoutes();
    }
    else
    {
        MessageBox.Show("Unable to add " + txtRoute.Text +
            CacheStatus.Message, "Uh oh", MessageBoxButtons.OK,
            MessageBoxIcon.Error);
    }
}

The key part of this code is in the creation of the new User.Route object and then calling Save() on that object to cause it to persist (to write it back to Caché's global).

To implement the Remove button we'll ask the selected item for its ID, and then call DeleteID on the Route class passing in the connection and the ID; the object will be removed from storage. (You might want to add a dialog box to make sure the user really wants to do this.)

private void btnRemove_Click(object sender, EventArgs e)
{
    if (lbRoutes.SelectedIndex < 0)
        return;
    string zombieID = lbRoutes.SelectedValue.ToString();

    // TODO: Add message box here to make sure they really
    // do want to delete this route
    User.Route.DeleteId(CacheConnection, zombieID);
    FillRoutes();
}

Note that no code is needed for the OK button; just set its DialogResult property to OK and it will automatically close the form when clicked.

The result is shown in Figure 14.

Add Route
Figure 14. Using the Add Routes form

You are now ready to create a nearly identical form for creating Medications, using very much the same logic. (It is left as an exercise for the industrious reader both to implement that form, and then to consider whether it makes sense to combine these two forms into a single parameterized form.)

The form for creating Patients is slightly more challenging. You'll want to gather the Name, PhoneNumber, and Doctor as strings (no problem) but you also need to be able to add and delete Prescriptions for this user. The form is shown in Figure 15.

Add Patient
Figure 15. Gathering strings: the Add Patient form

Your database does not have prescriptions; when you click the Add button you'll create a Prescription object and add it to the listbox (but you won't persist it until you save the person). To accomplish this, the Add button will open another form, using ShowDialog to open it modally, and then the Add button's event handler will check whether the DialogResult returned from the new form is DialogResult.OK. If so, you'll add the new prescription to the list (but not to the underlying storage).

This presents a bit of a problem. You don't want the new Prescription stored (the user may cancel the new patient) and you do want to display the name of the medication embedded in the prescription. To solve this, you'll create a nested class, PrescriptionHolder, and a type-safe list (using generics) called prescriptions, which will serve as the data source for your listbox. The PrescriptionHolder will have a public property that reaches into the Prescription, gets the Medication, and from that, gets the MedicationName property.

private class PrescriptionHolder
{
    private User.Prescription prescription;
    public User.Prescription ThePrescription 
    { 
        get { return prescription; } 
    }
    public string MedicationName 
    { 
        get { return prescription.Medication.MedicationName; } 
    }


    public PrescriptionHolder(User.Prescription prescription)
    {
        this.prescription = prescription;
    }
}

A second property makes the Prescription object itself available for retrieval. When you click Add next to the list of prescriptions, the AddPrescription window is opened, and that dialog gathers all the information needed to create a Prescription object, as shown in Figure 16.

Add Prescription
Figure 16. Add Prescription form

When you click the Save button, no event handler is invoked (the button is set to return DialogResult.OK). Instead, the prescription is created by the btnAdd_Click method back in AddPatient.cs.

private void btnAdd_Click(object sender, EventArgs e)
{
    AddPrescription dlg = new AddPrescription(cacheConnection);
    if (dlg.ShowDialog() == DialogResult.OK)
    {
        User.Prescription newPrescription = 
            new User.Prescription(cacheConnection);
        newPrescription.Medication = dlg.Medication;
        newPrescription.Route = dlg.Route;
        newPrescription.Dosage = dlg.Dosage;
        newPrescription.DispensingInfo = dlg.Dispense;
        prescriptions.Add(new PrescriptionHolder(newPrescription));
        BindPrescriptionList();
    }
}

private void BindPrescriptionList()
{
    lbPrescriptions.DataSource = null;
    lbPrescriptions.DataSource = prescriptions;
    lbPrescriptions.DisplayMember = "MedicationName";
    lbPrescriptions.ValueMember = "ID";
}

The only thing unusual in this form is the code to fill the two drop-down boxes:

private void FillLists()
{
    CacheCommand cacheCommand = User.Medication.FindAllMedications(cacheConnection);
    medicationTable = new DataTable();
    medicationTable.Load(cacheCommand.ExecuteReader());

    cbMedications.DataSource = medicationTable;
    cbMedications.DisplayMember = "MedicationName";
    cbMedications.ValueMember = "ID";

    cacheCommand = User.Route.FindAllRoutes(cacheConnection);
    routeTable = new DataTable();
    routeTable.Load(cacheCommand.ExecuteReader());

    cbRoute.DataSource = routeTable;
    cbRoute.DisplayMember = "RouteName";
    cbRoute.ValueMember = "ID";
}

This code creates a standard .NET DataTable for the list of medications, and a second for the list of Routes. Each DataTable serves as the DataSource for the drop-downs, and is filled in the following steps:

When the user clicks Save for the patient, all the information on the form is gathered into a new Patient object, which is then saved:

if ( patient == null )
{
    patient = new User.Patient(cacheConnection);
}
patient.Name = txtName.Text;
patient.PhoneNumber = txtPhoneNumber.Text;
patient.Doctor = txtDoctor.Text;
patient.Prescription.Clear();
foreach (PrescriptionHolder holder in prescriptions)
{
    patient.Prescription.Add(holder.ThePrescription);
}
CacheStatus cacheStatus = patient.Save();
if (cacheStatus.IsOK)
{
    patient = null;
}
else
{
    MessageBox.Show(
        "Unable to save " + txtName.Text + ": " +
        cacheStatus.Message,
        "Uh oh",
        MessageBoxButtons.OK,
        MessageBoxIcon.Error);
}
this.Close();

Notice that the first line creates a new patient only if one does not already exist. That allows the same window to be used to modify the user.

We'll now go back and create a crude dialog to let the user pick an existing user to edit, or create a new user by pressing the New button, as shown in Figure 17.

Pick Patient
Figure 17. PickPatient dialog

Remove CreatePatient from the first menu item, and create a second menu item, Patient. The event handler for this new menu item will open the dialog shown in Figure 17.

Clicking on one of the existing patients invokes AddPatient with that patient's ID, and closes the current window. The constructor for the PickPatient dialog fills the list of patients and sets an event handler for when the user clicks on a patient (we don't do this in the Properties window because we don't want this method inovked until the list is fully populated).

public PickPatient(CacheConnection cacheConnection)
{
    InitializeComponent();
    this.cacheConnection = cacheConnection;
    FillPatientList();
    lbPatients.SelectedIndexChanged +=
        new EventHandler(lbPatients_SelectedIndexChanged);
}

Filling the list requires a call to the FindAllPatients method that started out as a query in the Patients class in Caché Studio.

Notice that you don't wire up the SelectedChanged event until all the patients are added, otherwise each time you added a patient that event would fire.

private void FillPatientList()
{
    CacheCommand command = 
        User.Patient.FindAllPatients(cacheConnection);

    patientTable = new DataTable();
    patientTable.Load(command.ExecuteReader());

    lbPatients.DataSource = patientTable;
    lbPatients.DisplayMember = "Name";
    lbPatients.ValueMember = "ID";
    lbPatients.SelectedIndex = -1;
}

Invoking the AddPatient page when the user does choose a patient is as easy as picking up the value chosen, opening the new window, and closing the picker window:

private void lbPatients_SelectedIndexChanged(object sender, EventArgs e)
{
    AddPatient newWindow = 
        new AddPatient(cacheConnection, 
            lbPatients.SelectedValue.ToString());
    newWindow.Show();
    this.Close();
}

Finally, creating a new user is exactly the same code except that you call the constructor for AddPatient that takes a single parameter, the CacheConnection, and you do not pass in any value for the user ID (as you have none).

"What Have You Learned, Dorothy?"

As a C# programmer, I'm able to think about the objects (Patient, Medication, etc.) as C# objects, without ever worrying about their original nature as Caché objects, let alone their underlying true identity in the Caché Globals. This releases me from thinking about ADO.NET, DataSets, Rows, Tables and so forth. I think about creating objects, saving objects, and updating objects, and I let Caché, which is built on 40-year-old database technology, handle the details! Pretty cool.

The complete C# source code is available for download from my website.

The Data Does Exist

I'm quite happy to let Caché manage my data, and never worry about how it does so. That said, Caché Terminal allows me to inspect the data, if I'm so motivated, as shown in Figures 18 and 19.

Medication in Cache
Figure 18. Inspecting medication data with Caché Terminal

Patient In Cache
Figure 19. Inspecting patient data with Caché Terminal

Postscript: Looking Under the Hood

For those of you with unbridled curiosity, you certainly can look at the implementation provided by the proxy classes. For example, what follows is the code just for the Medication class!

namespace User {
    
    
    public class Medication : InterSystems.Data.CacheTypes.CachePersistent {
        
        public new const string ServerClassName = "User.Medication";
        
        static Medication() {
            InterSystems.Data.CacheClient.CacheConnection.AddClientTypeDescr("User.Medication", typeof(Medication));
        }
        
        public Medication() {
        }
        
        public Medication(InterSystems.Data.CacheClient.CacheConnection conn) {
            try {
                System.Threading.Monitor.Enter(conn);
                this.Init(conn, Medication.ServerClassName);
            }
            finally {
                System.Threading.Monitor.Exit(conn);
            }
        }
        
        public virtual string MedicationName {
            get {
                try {
                    System.Threading.Monitor.Enter(this.conn);
                    this.AssertIsConnected();
                    this.conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                    InterSystems.Data.CacheTypes.CacheMethodSignature mtdSignature = this.conn.GetMtdSignature();
                    try {
                        mtdSignature.SetReturnType(this.conn, 4);
                        this.GetPropertyS("MedicationName", 2, 0, mtdSignature);
                        return ((InterSystems.Data.CacheTypes.CacheStringReturnValue)(mtdSignature.ReturnValue)).Value;
                    }
                    finally {
                        mtdSignature.Clear();
                    }
                }
                finally {
                    System.Threading.Monitor.Exit(this.conn);
                }
            }
            set {
                try {
                    System.Threading.Monitor.Enter(this.conn);
                    this.AssertIsConnected();
                    this.conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                    InterSystems.Data.CacheTypes.CacheMethodSignature mtdSignature = this.conn.GetMtdSignature();
                    try {
                        mtdSignature.Add(value, this.conn, false);
                        this.SetPropertyS("MedicationName", mtdSignature, 2, 0, 2);
                    }
                    finally {
                        mtdSignature.Clear();
                    }
                }
                finally {
                    System.Threading.Monitor.Exit(this.conn);
                }
            }
        }
        
        public virtual InterSystems.Data.CacheTypes.CacheRelationshipObject PrescriptionToMedication {
            get {
                try {
                    System.Threading.Monitor.Enter(this.conn);
                    this.AssertIsConnected();
                    this.conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                    InterSystems.Data.CacheTypes.CacheMethodSignature mtdSignature = this.conn.GetMtdSignature();
                    try {
                        mtdSignature.SetReturnType(this.conn, 0);
                        this.GetPropertyS("PrescriptionToMedication", 4, 0, mtdSignature);
                        return ((InterSystems.Data.CacheTypes.CacheRelationshipObject)(((InterSystems.Data.CacheTypes.CacheObjReturnValue)(mtdSignature.ReturnValue)).Value));
                    }
                    finally {
                        mtdSignature.Clear();
                    }
                }
                finally {
                    System.Threading.Monitor.Exit(this.conn);
                }
            }
            set {
                try {
                    System.Threading.Monitor.Enter(this.conn);
                    this.AssertIsConnected();
                    this.conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                    InterSystems.Data.CacheTypes.CacheMethodSignature mtdSignature = this.conn.GetMtdSignature();
                    try {
                        mtdSignature.Add(value, this.conn, false);
                        this.SetPropertyS("PrescriptionToMedication", mtdSignature, 4, 0, 3);
                    }
                    finally {
                        mtdSignature.Clear();
                    }
                }
                finally {
                    System.Threading.Monitor.Exit(this.conn);
                }
            }
        }
        
        public static Medication Open(InterSystems.Data.CacheClient.CacheConnection conn, byte[] id, int concurrency, out InterSystems.Data.CacheTypes.CacheStatus status) {
            try {
                System.Threading.Monitor.Enter(conn);
                conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                return ((Medication)(conn.OpenProxyObj(Medication.ServerClassName, id, concurrency, out status)));
            }
            finally {
                System.Threading.Monitor.Exit(conn);
            }
        }
        
        public static Medication Open(InterSystems.Data.CacheClient.CacheConnection conn, byte[] id, out InterSystems.Data.CacheTypes.CacheStatus status) {
            try {
                System.Threading.Monitor.Enter(conn);
                conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                return ((Medication)(conn.OpenProxyObj(Medication.ServerClassName, id, out status)));
            }
            finally {
                System.Threading.Monitor.Exit(conn);
            }
        }
        
        public static Medication Open(InterSystems.Data.CacheClient.CacheConnection conn, byte[] id) {
            try {
                System.Threading.Monitor.Enter(conn);
                conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                return ((Medication)(conn.OpenProxyObj(Medication.ServerClassName, id)));
            }
            finally {
                System.Threading.Monitor.Exit(conn);
            }
        }
        
        public static Medication Open(InterSystems.Data.CacheClient.CacheConnection conn, byte[] id, int concurrency, int timeout, out InterSystems.Data.CacheTypes.CacheStatus status) {
            try {
                System.Threading.Monitor.Enter(conn);
                conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                return ((Medication)(conn.OpenProxyObj(Medication.ServerClassName, id, concurrency, timeout, out status)));
            }
            finally {
                System.Threading.Monitor.Exit(conn);
            }
        }
        
        public static Medication OpenId(InterSystems.Data.CacheClient.CacheConnection conn, string id, int concurrency, out InterSystems.Data.CacheTypes.CacheStatus status) {
            try {
                System.Threading.Monitor.Enter(conn);
                conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                return ((Medication)(conn.OpenProxyObj(Medication.ServerClassName, id, concurrency, out status)));
            }
            finally {
                System.Threading.Monitor.Exit(conn);
            }
        }
        
        public static Medication OpenId(InterSystems.Data.CacheClient.CacheConnection conn, string id, out InterSystems.Data.CacheTypes.CacheStatus status) {
            try {
                System.Threading.Monitor.Enter(conn);
                conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                return ((Medication)(conn.OpenProxyObj(Medication.ServerClassName, id, out status)));
            }
            finally {
                System.Threading.Monitor.Exit(conn);
            }
        }
        
        public static Medication OpenId(InterSystems.Data.CacheClient.CacheConnection conn, string id) {
            try {
                System.Threading.Monitor.Enter(conn);
                conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                return ((Medication)(conn.OpenProxyObj(Medication.ServerClassName, id)));
            }
            finally {
                System.Threading.Monitor.Exit(conn);
            }
        }
        
        public static Medication OpenId(InterSystems.Data.CacheClient.CacheConnection conn, string id, int concurrency, int timeout, out InterSystems.Data.CacheTypes.CacheStatus status) {
            try {
                System.Threading.Monitor.Enter(conn);
                conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                return ((Medication)(conn.OpenProxyObj(Medication.ServerClassName, id, concurrency, timeout, out status)));
            }
            finally {
                System.Threading.Monitor.Exit(conn);
            }
        }
        
        public static System.Nullable<bool> ExistsId(InterSystems.Data.CacheClient.CacheConnection conn, string id) {
            return Medication.ExistsIdInternal(conn, Medication.ServerClassName, id);
        }
        
        public static InterSystems.Data.CacheTypes.CacheStatus DeleteId(InterSystems.Data.CacheClient.CacheConnection conn, string id) {
            return Medication.DeleteIdInternal(conn, Medication.ServerClassName, id);
        }
        
        public static InterSystems.Data.CacheTypes.CacheStatus KillExtent(InterSystems.Data.CacheClient.CacheConnection conn) {
            return Medication.KillExtentInternal(conn, Medication.ServerClassName);
        }
        
        public virtual System.Nullable<bool> IDKEYCheck(string K1, System.Nullable<long> lockonly) {
            try {
                System.Threading.Monitor.Enter(this.conn);
                this.AssertIsConnected();
                this.conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                InterSystems.Data.CacheTypes.CacheMethodSignature mtdSignature = this.conn.GetMtdSignature();
                try {
                    mtdSignature.SetReturnType(this.conn, 9);
                    mtdSignature.Add(K1, this.conn, false);
                    mtdSignature.Add(lockonly, this.conn, false);
                    this.RunMethodS("IDKEYCheck", mtdSignature);
                    return ((InterSystems.Data.CacheTypes.CacheBooleanReturnValue)(mtdSignature.ReturnValue)).Value;
                }
                finally {
                    mtdSignature.Clear();
                }
            }
            finally {
                System.Threading.Monitor.Exit(this.conn);
            }
        }
        
        public virtual System.Nullable<bool> IDKEYExists(string K1, ref string id) {
            try {
                System.Threading.Monitor.Enter(this.conn);
                this.AssertIsConnected();
                this.conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                InterSystems.Data.CacheTypes.CacheMethodSignature mtdSignature = this.conn.GetMtdSignature();
                try {
                    mtdSignature.SetReturnType(this.conn, 9);
                    mtdSignature.Add(K1, this.conn, false);
                    mtdSignature.Add(id, this.conn, true);
                    this.RunMethodS("IDKEYExists", mtdSignature);
                    id = ((InterSystems.Data.CacheTypes.CacheStringArgument)(mtdSignature.Arguments[1])).Value;
                    return ((InterSystems.Data.CacheTypes.CacheBooleanReturnValue)(mtdSignature.ReturnValue)).Value;
                }
                finally {
                    mtdSignature.Clear();
                }
            }
            finally {
                System.Threading.Monitor.Exit(this.conn);
            }
        }
        
        public virtual InterSystems.Data.CacheTypes.CacheObject IDKEYOpen(string K1, System.Nullable<long> concurrency, ref InterSystems.Data.CacheTypes.CacheStatus sc) {
            try {
                System.Threading.Monitor.Enter(this.conn);
                this.AssertIsConnected();
                this.conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                InterSystems.Data.CacheTypes.CacheMethodSignature mtdSignature = this.conn.GetMtdSignature();
                try {
                    mtdSignature.SetReturnType(this.conn, 0);
                    mtdSignature.Add(K1, this.conn, false);
                    mtdSignature.Add(concurrency, this.conn, false);
                    mtdSignature.Add(sc, this.conn, true);
                    this.RunMethodS("IDKEYOpen", mtdSignature);
                    sc = ((InterSystems.Data.CacheTypes.CacheStatusArgument)(mtdSignature.Arguments[2])).Value;
                    return ((InterSystems.Data.CacheTypes.CacheObject)(((InterSystems.Data.CacheTypes.CacheObjReturnValue)(mtdSignature.ReturnValue)).Value));
                }
                finally {
                    mtdSignature.Clear();
                }
            }
            finally {
                System.Threading.Monitor.Exit(this.conn);
            }
        }
        
        public virtual string PrescriptionToMedicationGetObjectId(System.Nullable<long> force) {
            try {
                System.Threading.Monitor.Enter(this.conn);
                this.AssertIsConnected();
                this.conn.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                InterSystems.Data.CacheTypes.CacheMethodSignature mtdSignature = this.conn.GetMtdSignature();
                try {
                    mtdSignature.SetReturnType(this.conn, 4);
                    mtdSignature.Add(force, this.conn, false);
                    this.RunMethodS("PrescriptionToMedicationGetObjectId", mtdSignature);
                    return ((InterSystems.Data.CacheTypes.CacheStringReturnValue)(mtdSignature.ReturnValue)).Value;
                }
                finally {
                    mtdSignature.Clear();
                }
            }
            finally {
                System.Threading.Monitor.Exit(this.conn);
            }
        }
        
        public static InterSystems.Data.CacheClient.CacheCommand Extent(InterSystems.Data.CacheClient.CacheConnection conn) {
            InterSystems.Data.CacheClient.CacheCommand cmd = new InterSystems.Data.CacheClient.CacheCommand("{ call SQLUser.Medication_Extent}", conn);
            return cmd;
        }
        
        public static InterSystems.Data.CacheClient.CacheCommand FindAllMedications(InterSystems.Data.CacheClient.CacheConnection conn) {
            InterSystems.Data.CacheClient.CacheCommand cmd = new InterSystems.Data.CacheClient.CacheCommand("{ call SQLUser.Medication_FindAllMedications}", conn);
            return cmd;
        }
    }
}

The truth is, however, that you never need to look at this proxy implementation. You are free to pretend that Medication is just another C# class, and it works just as you would expect it to.

Jesse Liberty is a senior program manager for Microsoft Silverlight where he is responsible for the creation of tutorials, videos and other content to facilitate the learning and use of Silverlight. Jesse is well known in the industry in part because of his many bestselling books, including O'Reilly Media's Programming .NET 3.5, Programming C# 3.0, Learning ASP.NET with AJAX and the soon to be published Programming Silverlight.


Return to the Windows DevCenter.

Copyright © 2009 O'Reilly Media, Inc.