ADOX in Detail

About article. This article is about database creation and structure modification using MS ADOX library in a program. We'll discuss how to use ADOX for these purposes, some database object details and samples. Additional information you can find on MSDN site, there are some links to its resources in this text. Some database architecture advices are added for more complete problem understanding.

About ADOX. Microsoft ADOX (Active Data Objects Extension) is an ADO extension and, as written in MSDN, it is a companion library for ADO. Some ADOX objects uses ADO objects and interfaces but it is not necessary to know all ADO objects, methods and properties to use ADOX.

Same as ADO, ADOX is an interface level above OLE DB interface. It is wrapper for OLE DB objects. You can use ADOX and do not need to know something about OLE DB and providers. You just need to specify OLE DB provider, which will be used to process operations with database.

ADOX is a COM library. It can be used in any language and system that supports COM technology. It implements IDispatch interface so you can use it in script languages which support only late COM binding. In most cases it is used same way as ADO and if you are familiar with ADO, you won't get difficulties with ADOX. But sometimes you need some additional knowledge about databases to get result you want. We'll discuss some details in this article.

Samples are tested on operation system Windows 2000, databases Access2000 and MS SQL 2000, MDAC 2.6. Samples are written on VB because it takes less code and C programmers usually understand VB. VB samples in text are colored using one color, VC samples are placed between lines "VC specific-->" and "<--VC specific end" and colored using standard color schema of Visual Assist Add-in for VC . So VB programmers may omit such parts of article and VC programmers can pay attention to VC specific code. VC samples are created not for all cases but only where it is necessary. I hope that most VC developers can create C code from VB sample. If you think that it is better to add VC samples for all cases, please send email to me.

Table of content:

  1. Database creation and modification techniques
    1. Transact SQL language
    2. DAO
    3. ADOX
  2. ADOX Object Model
    1. ADOX collections
    2. ADOX objects
  3. How to add ADOX support to your program
  4. Catalog object
  5. Table object
  6. Column object
  7. Index object
  8. Key object
  9. View object
  10. Procedure object
  11. User object
  12. Group object

1. Database creation and modification techniques

If you want to create or modify database objects in your program you can use next ways:

a) Transact SQL language (MS SQL, Access 2000)

You can write T-SQL clause, which creates or alters table, columns, users etc. and execute it using ADO or other database access component. For example next clause "ALTER TABLE UserLogs Add Column newCol Number" adds to Access database new column "newCol" to table "UserLogs", type of "newCol" data values is Numeric. You can create other T-SQL clause to do other operations with database. Read SQL Books Online from MS SQL Server distributive for more details about T-SQL language.

b) DAO (Access)

Data Access Object is API for access to database access via MS Jet, ISAM or ODBC. It has objects that can be used to modify database objects and request data. DAO is optimized for MS Access database but supports other databases.

Later Microsoft created database support libraries based on OLE DB interfaces. For most database application developers it looks like DAO is splitted to 3 parts: ADO, ADOX and JRO, but of course, internal implementation differs from DAO. It is recommended to use new libraries instead of DAO in new projects. ADO is used to access to database data, ADOX to database structure, JRO for replication and compacting database. You can read how to migrate from DAO to ADO in MSDN. To see differences between DAO, ADOX and JRO see another article in MSDN

c) ADOX

ADOX can be used not only for Access or SQL but also for all databases that have OLE DB provider. Some ADOX methods and properties may be not supported by provider but most common operation usually can be performed. Next article we'll discuss MS SQL and Access compatibility with ADOX and get application to test operations supported by specific provider.

2. ADOX Object Model

a) ADOX collections

All ADOX collections have next methods: Append, Delete, Refresh and properties: Count, Item. Like most COM collections, ADOX collections have default function if you use it in VB or VBScript. Item property is default for ADOX collection. So you can omit word Item in your program: collName.Item("first") is the same as collName("first"). You can refer to collection item using either its name like above or order number collName(0). First item has number 0, second 1, last collName.Count 1.

If you want to enumerate all objects in a collection you can use For cycle in two ways:

1) Using Each keyword (VB, VBScript)

    For Each dbItem In dbCollection
        'do smth. e.g. MsgBox dbItem.Name
    Next

2) Using Count property

    Dim ind As Integer
    For ind=0 To dbCollection.Count - 1
        ' do smth. e.g. MsgBox dbCollection.Item(ind).Name
    Next

VC specific ---------->
VC has no keyword Each but you can use method _NewEnum to get next item in collection. Usually it is more convenient to enumerate items by index.

    long ind;
    for (ind = 0; ind< dbColl->Count; ind++)
        MessageBox(dbColl->GetItem(ind)->GetName());
<------- end of VC specific

Some objects in ADOX have collection "Properties" which consists of provider-specific properties for object.

b) ADOX objects.

There are next objects in ADOX library: Catalog, Table, Column, Index, Key, Group, User, Procedure and View. Objects Procedure and View cannot be created as standalone object using New operator in VB or CoCreateInstance in VC because they don't have public class factory. These objects can be created by corresponding collections Procedures and Views.  Next picture from MSDN shows ADOX object hierarchy (object Command is not ADOX object, it is ADO library object).

You can see that Columns collection is a property of objects Table, Index and Key. Also Users and Groups collections have cross-references. But objects in different hierarchy level usually have different properties values. E.g. property SortOrder for Column object under Table and Key has undefined value but may have value for Index object.

Objects Table, Index and Column have collection Property with provider-specific properties values. Remember that Properties are provider-specific. Some provider supports one set of properties, other providers support other set. First make sure that provider supports necessary property or find same property name for other provider. Do not confuse with Properties collection that are provider specific collection and objects' fixed properties (name, type etc.) Once more, fixed properties are common for all providers but Properties collection contains provider-specific properties.

If provider doesn't support property which you want to set value, error will occur. If you add new object with such property defined, error will occur when you try to add it to collection, if you try to set property for existing object error will occurred when you try to refer to it. MSDN recommends reading provider documentation to get information about supported properties.

All ADOX objects are "passive" objects. I mean that you can change its state or some properties but they do not do some operations. You can create them and add into collection or remove. ADO has "active" objects that fetch records from database, change its order etc. You can see that only three ADOX objects have methods in addition to properties. But these methods only get and set owner for database objects (methods GetPermissions and SetPermissions for objects Catalog, User and Group; method SetPassword of object User) and as a matter of fact they also change property of objects. "Active" entities in ADOX are object collections. ADOX object collections create and delete database objects. It is good enough software architecture. Of course, it is some kind of "philosophy" notes and usually it is not important for your application.

To see more details about ADOX object model see MSDN article.

3. How to add ADOX support to your program.

If you use Visual Basic, you can add ADOX to your program choosing menu Project\References. ADOX is a COM library so you don't need to create Declare directive in VB. In the list of COM objects under this menu item try to find record "Microsoft ADO Ext. 2.6 for DDL and Security" or same (your version may differ) and check it on. If your list is very big and you can't find it try to add reference using "browse" button. Usually file is placed in same directory as ADO: "Program Files\Common Files\System\ADO\msadox.dll". If you can't find this library, install MDAC from Microsoft site and then add reference to it.

VC specific begin---->

If you use Visual C, use directive #import. Next two lines show sample of this directive for both libraries ADO and ADOX. In case that you need to use only ADOX library, do not write first line that adds reference to ADO library.

#import "C:\Program Files\Common Files\System\ADO\msado15.dll" rename("EOF", "EndOfFile")
#import "C:\Program Files\Common Files\System\ADO\msadox.dll"

Note. Do not use no_namespace attribute for #import directive if you use both ADO and ADOX. Some names are declared in both libraries. In this case you need to specify namespace before object library name e.g. ADOX::Table. Other variant is to use rename attribute in #import directive same as for ADO in sample above but you need to enumerate all double defined names and its new synonyms.

When you import COM library VC creates for you special classes that wraps COM objects in library. It is very useful directive with some attributes. I recommend you to read about it in MSDN if you do not use it. There are 2 types of wrapper classes: one of them is typed with Ptr suffix (_CommandPtr, TablePtr etc.) and second is without suffix. First class has some functions which checks result of called function, raises exception if necessary, returns value for properties etc. To understand wrappers you can place break point on method call and go into method (F11 key) or analyze .tli and .tlh files. You can see next example of wrapper class usage:

void VCsample1(ADOX::TablesPtr tblColl)
{
    ADOX::_Table *pTbl1; // without suffix
    ADOX::_TablePtr pTbl2; // with suffix
    // tblColl is a collection of tables, get first table from collection 
    pTbl2 = tblColl->GetItem((long)0);
    // get name of table. Tree lines below calls one function.
    //Second line Name is a synonym to GetName().
    //Third line also is transformed into call of function GetName.
    _bstr_t tN0 = pTbl2->GetName();
    _bstr_t tN1 = pTbl2->Name;
    _bstr_t tN2 = tblColl->Item[(long)0]->Name;
    BSTR tN3;
    HRESULT hr;
    pTbl1 = tblColl->GetItem((long)0);
    // get name of table. Next two lines calls raw interface functions.
    hr = pTbl1->get_Name(&tN3);
    SysFreeString(tN3);>
    hr = pTbl2->get_Name(&tN3);
    SysFreeString(tN3);
    pTbl1->Release();
    pTbl2->Release();
}

Use method, which is more suitable for you. I prefer in most cases wrapper functions. Note that you cannot omit GetItem method call unlike in VB. VC ignores "default" method declaration in COM library typelib and you need to tell to compiler what you do want exactly. And don't forget to release objects when you don't need them more. VB adds these calls where it is necessary (it has Garbage Collector subsystem).

<---- VC specific end

4. Catalog object.

Catalog object is a database instance object. It is first object, which you need to use in your ADOX program. Its collections represent database objects: Tables, Views, Procedures, Users, and Groups. Every database object is included in corresponding collection. If there is no object of some type in database (e.g. database is empty) collection of this type is empty.

Catalog object property:

  • ActiveConnection ADO Connection object which defines database connection parameters.

Methods:

  • Create creates new database;
  • GetObjectOwner returns String user or group name of database object owner;
  • SetObjectOwner sets new or changes existing owner of database object.

Catalog is high-level object and if we want to modify some database object we need first to create Catalog object: either create new database or connect to existing one.

Connect to existing database:

Public Function initialize(connectionSting As String)
Dim dbMan As ADOX.Catalog
    On Error Resume Next
    Err.Clear
    Set dbMan = New ADOX.Catalog
    If Err.Number <> 0 Then
        MsgBox "cannot create ADOX object. Please install MDAC from Microsoft site"
    Else
        dbMan.ActiveConnection = connectionSting
        If Err.Number <> 0 Then
            MsgBox "Cannot initialize connection. Please check connection string"
        End If
    End If
End Function

Property ActiveConnection is ADO Connection object. As far as Connection object has default property ConnectionString which type is String line dbMan.ActiveConnection = connectionSting sets property ConnectionString of object dbMan.ActiveConnection to some value. To set Connection object to property ActiveConnection you can use code Set dbMan.ActiveConnection = connectionObject.

Create new database method sample:

Public Function createDb(connectionSting As String)
Dim dbMan As ADOX.Catalog

    On Error Resume Next
    Set dbMan = New ADOX.Catalog
    If Err.Number <> 0 Then
        MsgBox "cannot create ADOX object. Please install MDAC from Microsoft site"
    Else
        dbMan.Create connectionSting
        If Err.Number <> 0 Then
            MsgBox "Cannot create database. Please check connection string"
        End If
    End If
End Function

Connection string in this case must point to not existing file. If file already exists you'll get error message.

Connection string for ADOX have same format as for ADO. E.g."Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\new.mdb". If you don't know how to create connection string you can read about it in MSDN or create file with extension "udl", e.g. "connection.udl" and double click left button on it in explorer. You'll see dialog box with some tabs on it. Specify provider, file and other information. Then view it in a text editor (e.g. notepad). You'll get connection string to chosen file or DSN.

Methods GetObjectOwner and SetObjectOwner are used to get and change owner of database object. For example, if you want to delete user from database, you have to reassign its objects to other user or delete them at all. SetObjectOwner may be used to change object owner.

Sub changeTableOwner(dbCatalog As ADOX.Catalog)
Dim ownerName As String
    On Error GoTo errCatch
    ownerName = dbCatalog.GetObjectOwner("Table1", adPermObjTable)
    dbCatalog.SetObjectOwner "Table1", adPermObjTable, "Admin"
    Exit Sub
errCatch:
    MsgBox Err.Description
End Sub

Let's see methods description.
Owner = Catalog.GetObjectOwner(ObjectName, ObjectType [, ObjectTypeId])
Returns a String value that specifies the Name of the User or Group that owns the object.

Catalog.SetObjectOwner ObjectName, ObjectType , OwnerName [, ObjectTypeId]
Specifies the owner of an object in a Catalog.

Parameters:

ObjectName - a String value that specifies the name of the object for which to return the owner.
ObjectType - a Long value which can be one of the ObjectTypeEnum constants, that specifies the type of the object for which to get the owner.
OwnerName - a String value that specifies the Name of the User or Group to own the object.
ObjectTypeId - optional. A Variant value that specifies the GUID for a provider object type not defined by the OLE DB specification. This parameter is required if ObjectType is set to adPermObjProviderSpecific; otherwise, it is not used.

Remarks

An error will occur if the provider does not support returning object owners.

Unfortunately in my case (ADOX 2.6, both providers Jet 4.0 and SQLOLEDB) providers do not support this methods. I get error number 0x800A0CB3 ("Object or provider is not capable of performing requested operation") for SQLOLEDB and error number 0x80004005 for Jet 4.0 provider ("Unspecified error").

ObjectTypeEnum constants have next values:

Constant

Description

adPermObjColumn

The object is a column.

adPermObjDatabase

The object is a database.

adPermObjProcedure

The object is a procedure.

adPermObjProviderSpecific

The object is a type defined by the provider. An error will occur if the ObjectType parameter is adPermObjProviderSpecific and an ObjectTypeId is not supplied.

adPermObjTable

The object is a table.

adPermObjView

The object is a view.

You can see that Catalog object do not have properties same as DAO Database object properties (Version, Name, Updatable, Replicable etc.). Some databases don't support same properties and as far as ADOX is universal library for different databases, it doesn't support such properties.

Lets suppose you create application, which works with Access database, and database structure may be changed from one version of your program to other. You need to determine which database structure you have. First variant is checking database structure features which define database version, e.g. if some table exists in one version of database and does not exist in other, you can check whether this table exists in unknown version of database. Second variant is creating special table, which contain database version. I think that second variant is very ugly solution, first variant much better.

Let's enumerate all database tables and users using Catalog object collection.

Private Sub enumerateTableCollections(dbCatalog As ADOX.Catalog)
Dim dbTable As ADOX.Table
Dim dbUser As ADOX.User
Dim itemsNo As Integer
Dim str As String

    str = "There are " & dbCatalog.Tables.Count & " tables" & vbCrLf
    For Each dbTable In dbCatalog.Tables
        str = str & dbTable.name & " "
    Next
    MsgBox str

    On Error Resume Next

    str = "Users:" & vbCrLf
    itemsNo = dbCatalog.Users.Count
    If (Err.Number = 0) And (itemsNo > 0) Then
        For Each dbUser In dbCatalog.Users
             str = str & dbUser
        Next
    Else
        str = str & "there are no"
    End If
    MsgBox str
End Sub

You can see differences between cycles. In first case we get items from Tables collection without checking collection length (items count). We just get collection item using For Each cycle. It is normal usage of collection. If collection is empty this cycle won't be executed. But realization of Users and Groups collections in ADOX 2.6 possible has bugs. Workaround of this problem is to check length of collection (items count). If collection is empty it causes error when you refer to Count property. So we need to catch error. We placed On Error Resume Next statement and check Err.Number value. If there is no error collection is not empty and vise versa.

I tried to analysis this problem and emulated For Each cycle in VC but didn't find errors yet. Possible it takes more detailed analysis. We'll talk about these collections more detailed in next article.

You can walk through other collections like in sample above. You can find procedure which work with all collections in a file with sample code.

Begin of VC specific -------->

Sample code on VC that make same operations. See inline comments in sample text.

//this procedure emulates For Each cycle in VB and gets
collection items' name
void enCollections(ADOX::_Collection *pColl)
{
    IEnumVARIANT *pEnum;
    ULONG lFetched;
    VARIANT var;
    pColl->raw__NewEnum((IUnknown**)&pEnum);// get enumerator object
    for ( ; ; )
    {
    //clear variable which will contain collection item object
        VariantInit(&var);
        // Next method returns IDispatch encapsulated into VARIANT variable
        // if VARIANT type is VT_EMPTY then collection is empty
        pEnum->Next(1, &var, &lFetched);
        if (var.vt== VT_EMPTY) break;
        if (var.vt== VT_DISPATCH)
        {
            DISPID dspId;
            HRESULT hr;
            OLECHAR *propertyName = L"Name";
            // Since we work with IDispatch interface we need to know DISP_ID 
            //of "Name" property
            hr = var.pdispVal->GetIDsOfNames(IID_NULL, &propertyName, 1, 
                LOCALE_SYSTEM_DEFAULT, &dspId);
            if (SUCCEEDED(hr))
            {
                VARIANT vVal;
                // clear variable which will contain property value
                VariantInit(&vVal);
                DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
                // get property value
                hr = var.pdispVal->Invoke(dspId, IID_NULL, LOCALE_USER_DEFAULT, 
                     DISPATCH_PROPERTYGET,&dispparamsNoArgs, &vVal, NULL, NULL);
                if (SUCCEEDED(hr) && VT_BSTR == vVal.vt)
                {
                    char ascName[256];
                    memset(ascName, 0, 256);
                    // convert UNICODE string to ASCII string
                    WideCharToMultiByte(CP_ACP, 0, vVal.bstrVal, -1, _
ascName, 256, NULL, NULL);
                    MessageBox(NULL, ascName, "Name", 0);
                }
            }
            // release collection item object
            var.pdispVal->Release();
        }
        // clear object container variable
        VariantClear(&var);
    }
    // release resources
    pEnum->Release();
    VariantClear(&var);
}

void CADOXDBwalkerDlg::enumerateCollections() 
{
    CoInitialize(NULL);
    ADOX::_CatalogPtr pDbCatalog;
    ADOX::Users *pUsrColl;
    ADOX::Views *pVwColl;
    ADOX::TablesPtr tblColl;
    try
    {
        HRESULT hr = pDbCatalog.CreateInstance(__uuidof(Catalog));
        _variant_t connStr(L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\newDb.mdb;");
        //set ActiveConnection property
        pDbCatalog->PutActiveConnection(connStr);
        // get Tables collection and enumerate its items via IDispatch interface
        // same as VB process For Each cycle
        tblColl = pDbCatalog->Tables;
        enCollections(tblColl);
        // get collection items count
        long tblCount = tblColl->Count;
        // other way. 
        long ind;
        for (ind = 0; ind< tblColl->Count; ind++)
            MessageBox(tblColl->GetItem(ind)->GetName());
        pVwColl = pDbCatalog->Views;
        enCollections(pVwColl);
        long vwCount = pVwColl->Count;

pUsrColl = pDbCatalog->Users; enCollections(pUsrColl); long usrCount = pUsrColl->Count; } catch (_com_error & er) { MessageBox(er.Description(), "error"); } CoUninitialize(); }

<-------- end of VC specific

All ADOX collections have method Append that adds new item to collection. You can read how to add new table, view, procedure, user and group to corresponding collection in next paragraphs.

5. Table object

Table is one of basic concept of database and simplest databases may be represented as tables set. When database is created first of all tables structure is created. Lets discuss this important object in ADOX.

ADOX Table object represents database table or view. In other words, in ADOX views are tables also. View in some point is same as table (you can get data from both table and view same way and even change data for some views) and possible this is a reason why views can be represented by Table object.

Properties:

Name database table name, unique in Tables collection;
Type - type of table: users' table, system table etc.(read only);
ParentCatalog reference to parent Catalog object which table belong;
DateCreated and DateModified - date when table was created and last modification date (read only).

Collections:

Columns - table columns;
Indexes - table indexes;
Keys - table keys;
Properties - provider-specific table properties.

As we discussed above, Table object can refer to database table or to database view. Property Type is a string value, which shows table type, e.g. "VIEW", "TABLE", "ACCESS TABLE", "SYSTEM TABLE" etc. Users' tables have Type "TABLE". This property is read only and you can't set table type, it is set by ADOX automatically. If you create new table it have type "TABLE" i.e. it is user-defined table.

Table object has provider-specific collection Properties. When you create new Table and append columns or indexes to it, you may access to such properties for new objects. But first you need to specify provider. You can do it in ADOX using one way. Set property ParentCatalog to existing Catalog object already connected to some database. ADOX will get provider type from Connection property of Catalog object. If you do not set ParentCatalog property Properties collection is empty.

Public Sub newTable(db_Table As String, dbCatalog As ADOX.Catalog)
    Set dbTable = New ADOX.Table
    dbTable.name = db_Table
    Set dbTable.ParentCatalog = dbCatalog
    dbTable.Columns.Append "newColumn"
    dbTable.Columns("newColumn").Properties("Autoincrement")=True
End Sub

Next table shows which items in Properties collection of Table object is available for Access Jet OLE DB provider v4. All items are typed with necessary space symbols and same case as they are named in collection.

Property name Property type Read/Write
Temporary Table adBoolean Read only
Jet OLEDB:Table Validation Text adBSTR R/w
Jet OLEDB:Table Validation Rule adBSTR R/w
Jet OLEDB:Cache Link Name/Password adBoolean R/w
Jet OLEDB:Remote Table Name adBSTR R/w
Jet OLEDB:Link Provider String adBSTR R/w
Jet OLEDB:Link Datasource adBSTR R/w
Jet OLEDB:Exclusive Link adBoolean R/w
Jet OLEDB:Create Link adBoolean R/w
Jet OLEDB:Table Hidden In Access adBoolean R/w

Next article we'll discuss Properties more detailed.

Lets talk about Table collections. Columns collection contains objects that represent database columns. You already know how to access to existing collection item. If you want to add new Column object you can do it by name or by object reference. In sample above we added column by name. In this case we will get column with default properties (data type, length etc.) To add column with different properties you need first to create Column object and set its properties to some values and only then append it. Some properties you can add after appending new column to collection (e.g. DefinedSize) but if it is possible in your program use common way: create object, set its properties and then add it to collection.

Other collections Indexes and Keys are same as Columns collection except some nuances. If you want to append index or column by name without creating object same as in sample above we created column object, you need to specify other parameters. As a matter of fact parameters called as optional are not optional in such case. As we told above Append method has 2 possible usages: adding item by name and by reference to existing object. In second case parameters may be optional if you have specified all necessary properties for Index or Key object. You can use them but if you do mistake and parameters of object will conflict with parameters that you specify in Append function call, you'll get an error message. If you add object by name and do not create standalone object before add it to collection, you need to define all necessary parameters. Which parameters are necessary we'll tell in next paragraphs [7, 8].

This situation with collection as far as I see is a lack of ADOX collection design. For some experienced programmers it may be more flexible to use such design but potentially it may cause some bugs, which at first sight not very easy to catch. Use carefully Append method. It is better to use one programming technique with it. Either add all objects by name and all necessary parameters or create object, set its properties and then add it without additional parameters.

We know how refer to existing table and create new one. Next step is to add new table to database. As it was documented in MSDN, it is possible to add new table by its name or by refer to new Table object same as columns and indexes discussed above. But unfortunately I couldn't force ADOX 2.6 to add Table by name. It returns an error. But we know other way how to create Table object and set its properties. Next operation adds it to collection:

    dbCatalog.Tables.Append dbTable

6. Column object

Column object refer to database column in a table. Also Column may be a part of Keys or Indexes collections in ADOX object model. Table Column object differs from key Column and index Column thought they all have same properties (except provider-specific) but some properties have empty value. It depends on collection, which Column belongs.

Properties:

Name - column name, it has to be unique in a Columns collection.
Type - type of column data: Integer, String etc.
ParentCatalog - pointer from column to catalog, which owns column
Attributes - specify whether column can contain nulls or be fixed-length
DefinedSize maximum size of column data (in bytes)
NumericScale - specify the scale for data types Numeric and Decimal
Precision specify the maximum precision for numeric data value
RelatedColumn - specify the name of the related column in the related table, for key columns
SortOrder - specify whether the sort order is ascending or descending for index columns.

Collection:

Properties - provider-specific column properties.

Most properties are valid for table columns only (name have defined value for all collections). RelatedColumn property have value only for columns that belong to Key collection Columns, SortOrder property have value for Index collection Columns. First let's talk about Table columns properties.

Type is a value that defines column data type (e.g. Integer, Text, Date etc.) Type values are set of some predefined integer values. ADOX library defines special data type called DataTypeEnum. DataTypeEnum article in MSDN. This type is subset of integer values of Variant variable type (integer value which VB function VarType returns). Values of Type are provider specific and not all values from DataTypeEnum type can be set to this property.

Available types for Access provider Jet:

Type property value (ADOX) Column data type (Access)
adBoolean Yes/No
adInteger Number, LongInteger
adSmallInt Number, LongInt
adUnsignedTinyInt Number, Byte
adNumeric Number, Decimal
adDecimal Number, Decimal
adDouble Number, Double
adSingle Number, Single
adCurrency Currency
adBinary Binary
adVarBinary Binary
adLongVarBinary OLE Object
adWChar Text
adVarWChar Text
adLongVarWChar Memo
adGUID Binary
adDate DateTime

We'll discuss data types in ADOX more detailed in next article.

Attributes property for Column object specify next data attributes for column: adColFixed (the column is a fixed length), adColNullable (column can contain null values) and 0 (default, there are no attributes).

Property DefinedSize specifies maximum size for column data. Use this property for types that may have different size (Text and Binary in Access). For other types provider may ignore its value. You can try to set this property for column type adInteger to 100 but field size in Access as a matter of fact will be 4 bytes. You need to use Type property to set size of numeric column (LongInteger or Byte).

Property NumericScale ignored for all column data types except adNumeric and adDecimal. It specifies number of decimal places to the right of decimal point.

As written in MSDN property Precision used only for numeric data types. Provider can ignore this value for some types. E.g. Access do not supports precision=2 for adLongInt data type. I tried to create column with this values but when I read this property value after appending new column, it was equal to 10. I think this value is important for adNumeric and adDecimal data types only.

Property RelatedColumn can be used for columns used in references to other tables. If database table has foreign key reference to other table, each column, which included into foreign key has its related column. In this case property RelatedColumn has some sensible value. For columns in other type of key, columns in Index and Table objects collections this property either has empty value of type Variant or empty BSTR string.

Property SortOrder has defined value for columns in table Index object collection. Its values are adSortAscending (default) and adSortDescending. It defines how column data is sorted in the index.

 Read about indexes in next paragraph ([7]).

Provider-specific properties collection Properties depends not only on provider but also on type of column collection container object (Table, Index and Key). E.g. Index and Key columns don't have items in Properties collection for my ADOX database but Table columns have items in this collection.

Next table shows which items in Properties collection of Column object is available for Access Jet provider v4. All items are typed with necessary space symbols and same case as they are named in collection.

Property name

Property type

Read/Write

Autoincrement

adBoolean

R/w

Default

adVariant

R/w

Description

adBSTR

R/w

Nullable

adBoolean

R/w

Fixed Length

adBoolean

R/w

Seed

adInteger

R/w

Increment

adInteger

R/w

Jet OLEDB:Column Validation Text

adBSTR

R/w

Jet OLEDB:Column Validation Rule

adBSTR

R/w

Jet OLEDB:ISAM Not Last Column

adBoolean

R/w

Jet OLEDB:AutoGenerate

adBoolean

R/w

Jet OLEDB:One BLOB per Page

adBoolean

R/w

Jet OLEDB:Compressed UNICODE Strings

adBoolean

R/w

Jet OLEDB:Allow Zero Length

adBoolean

R/w

Jet OLEDB:Hyperlink

adBoolean

R/w

You can see that some numeric types, which in Access have type Number and subtypes (Byte, Integer, LongInt etc.) in ADOX are different types. Some ADOX types represented in Access as one type (adVarBinary and adBinary). Type Hyperlink in ADOX has additional property in Properties collection. Sometimes before create columns look at tables above and analyze them.

Note. Unique column values is not a column property but property of its key (see next paragraphs). So if you want to create unique constraint, create unique key. Insert duplicate values into column will create error.

And finally lets see VB sample.

Sub addNewTable
Dim dbTable As New ADOX.Table

    On Error Resume Next

    dbTable.name = "myNewTable"
    Set dbTable.ParentCatalog = dbCatalog

' we set ParentCatalog property to access to 
' provider-defined properties for Column object 
' If we don' t use Properties collection before appending 
' table, we don't need to set this property

    dbTable.Columns.Append "id_Column", adInteger
    dbTable.Columns.Append "newColumn", adInteger
    dbTable.Columns("id_Column").Properties("Autoincrement") = True
    If Err.Number <> 0 Then
        MsgBox Err.Description & Err.Number
        Err.Clear
    End If    

    dbCatalog.Tables.Append dbTable
    If Err.Number <> 0 Then
        MsgBox Err.Description & Err.Number
        MsgBox Err.Description & Err.Number, vbOKOnly, "error", Err.HelpFile()
        Err.Clear
    End If
    
    dbTable.Columns("newColumn").Type = adWChar 
' we'll get error because we set column type 
' after appending it to table
    If Err.Number <> 0 Then
        MsgBox Err.Description & Err.Number
        Err.Clear
    End If

    Dim mColumn As New ADOX.Column
    mColumn.name = " newColumn2"
    mColumn.Properties("myProp") = "myVal"
    dbTable.Columns.Append mColumn

' we'll get error because property "myProp" doesn't 
' exists in Properties collection.
' but column will be added to table

    If Err.Number <> 0 Then
        MsgBox Err.Description & Err.Number
        Err.Clear
    End If
End Sub

7. Index object

First lets talk about database indexes. Sometimes developers have troubles because they don't understand what are indexes for. Index is a special database structure, which allow searching records faster than using simple table scan. Index can be created on one or more columns in table. Column values may differ from one record to another. When database engine search records with certain column value it have to compare column values of records with defined value in SQL clause. This situation is same as search item with necessary value in array or linked list. Scan method takes to see all items in worse case. More smart methods are used to increase performance. For large data    storages (like database) more effective is creating B-Tree or other tree structure and searching elements in it.

Key field (I mean value which is used to search item, not a database key) for index tree nodes may be different, it depends on database engine. But it is important that this key is created depending on column value. There are so many different key values for tree as many different values for table column. If database column has Boolean type (only two values) tree has only two nodes and it takes other additional methods to find necessary record. But if database field has unique values it is good case for index creation. Sure, you can create key for not unique columns. For this case database engine possible creates additional structure to each tree node, which has more than one column reference. For example, this structure may be a list of column references.

Key can be created not for single column but for a group of columns. Key field for tree nodes created depending on all column values. For example, integer and Boolean columns make good enough key for index tree if integer column values range is big enough.

It is not important for you how indexes are implemented in database engine, which you use (if you are not database engine developer) but if you know a little about it you can create indexes that really increase performance and won't increase memory treatments. Use indexes only where it is necessary, do not create indexes for memo fields, do not create indexes for all possible combinations of columns etc. Analyze your application SQL requests statistics and only then create necessary index.

Note. Key field for tree, which we told above, is not the same as database key. It is standard term. You can read article about B-Tree.

Let's return to ADOX Index object.

Properties:

Name index name;
Unique indicates whether the index keys must be unique;
PrimaryKey indicates whether the index is the primary key for a table;
IndexNulls indicates whether records that have null values in their index fields have index entries;
Clustered indicates whether the index is clustered.

Collections:

Columns columns that index consist of;
Properties provider-specific properties.

Unique property type is boolean. It only specifies index structure type. It tells to database engine whether create simple B-Tree for unique columns or add list to B-Tree nodes.


PrimaryKey is also boolean type property. Only one index may be primary key index for a table because only one primary key is allowed. As a matter of fact, usually keys have corresponding indexes e.g. primary key. But not every index has a corresponding key.


Property IndexNulls has next values (MSDN):

adIndexNullsAllow

The index does allow entries in which the key columns are null. If a null value is entered in a key column, the entry is inserted into the index.

adIndexNullsDisallow

Default. The index does not allow entries in which the key columns are null. If a null value is entered in a key column, an error will occur.

adIndexNullsIgnore

The index does not insert entries containing null keys. If a null value is entered in a key column, the entry is ignored and no error occurs.

adIndexNullsIgnoreAny

The index does not insert entries where some key column has a null value. For an index having a multi-column key, if a null value is entered in some column, the entry is ignored and no error occurs.

Property Clustered means that records in table has same physical sort order as index. I.e. records are stored in a database in order defined by index. You can create only one clustered index per a table.

You can set properties of object Index only before you append it to database. If Index object already exist in collection Indexes, its properties are read only. When you add index to database it creates all necessary structures for existing data. To change Index object property you need first to delete it from collection and then create new one. It takes some time to delete and create index. Also if you want to create clustered index on a table, which already has data, it will also take some time to reorganize table physical storage.

You can read articles by Dusan Petkovic to know more about indexes: Indexing for Sort Performance and Which is Faster: Index Access or Table Scan? and other (e.g. Tips on Optimizing SQL Server Indexes)

VB Sample:

Public Sub newIndex(dbTable As ADOX.Table)
'  dbTable is table where we add new index
Dim dbIndex As ADOX.Index
    On Error GoTo errCatch

    Set dbIndex = New ADOX.Index
    dbIndex.name = "tblPrimaryIndex"
    dbIndex.Clustered = True
    dbIndex.IndexNulls = adIndexNullsDisallow
    dbIndex.PrimaryKey = True
    dbIndex.Unique = True

    dbIndex.Columns.Append "id_Column" 
    ' id_Column is a name of column

    dbTable.Keys.Append dbIndex

    dbTable.Indexes.Append "column2Index", "newColumn2" 
    ' newColumn2 is name of column in our table
    Exit Sub

errCatch:
        MsgBox Err.Description & Err.Number
End Sub

8. Key object

Key means something that identifies record. For example, table column has unique values in a table. This column may be called as key. Key can consist from some columns. There are primary and foreign keys.

Primary key is a key which value is unique for table and number of its columns less than number of columns in other unique column combination values. In most cases primary key is one column.

Records form one table may refer to other (there is relationship between these columns). In this case column, which refers to other table, is called "foreign key". Usually foreign key refers to primary key in other table. But sometimes it refers to column(s) that is not a primary key. Such column in referred table is usually called "secondary" (in ADOX "unique").

Key almost always used in reference to "constraint" term. Constraint is some kind of restriction. Primary key is column(s) unique value constraint. Primary key cannot have NULL value. Foreign key constraint usually means that column value refers to only one column in other table, NULL values also are not allowed. Foreign key used to force database integrity. Foreign key constraint can perform cascade deleting rows with foreign key value when referred row in primary table is deleted.

When you add key to table, provider can add index for this key, e.g. Access Jet adds index for columns, which compose key. It may be done to increase database performance. But if you read Dusan Petkovic article "Which is Faster: Index Access or Table Scan?" you know that indexes do not always increase performance.

ADOX Key object properties:

Name key name;
Type key type: primary, foreign or unique;
RelatedTable name of related table;
DeleteRule action performed on delete primary key;
UpdateRule - action performed on update primary key.

Collection:

Columns database key columns.

Property Type can have values: adKeyPrimary (default, for primary keys), adKeyForeign (foreign key), adKeyUnique (for unique constraints in a table).

RelatedTable property has valid value only for foreign keys. It doesn't make sense for other types of keys.

DeleteRule and UpdateRule properties are constants of type RuleEnum (MSDN):

adRICascade

Cascade changes.

adRINone

Default. No action is taken.

adRISetDefault

Foreign key value is set to the default.

adRISetNull

Foreign key value is set to null.

Type of columns used in relationship between tables must be same. You cannot refer from Text column to Integer one and from Integer to LongInt. You'll get error in such case. Also do not create reference from not nullable column to nullable, i.e. column types and some attributes must be same.

If you add relationship from unique key to unique key you'll get relation One-To-One. Relation from unique column to not unique is One-To-Many relationship. Table with unique column in this case is called "primary". Relation from not unique column to not unique one is a nonsense. You cannot create standard constraint for such references. If you want to create relation Many-To-Many, create additional intermediate table for references from first table to second and create references One-To-Many from first table to intermediate table and One-To-Many from second table to intermediate. Both first and second tables have unique columns, intermediate table has not unique columns.

When you add foreign key first you have to create index on a column in other table. ADOX expects that you add reference to column, which has unique values. If referred column do not have unique index, error occurs "no unique index found for the referenced field of the primary table". Primary table is table, which you refer to, table where you create key is secondary. When you create unique index over non-unique, ADOX do not automatically delete existing index. You'd better do it yourself.

Next sample works for Jet OLE DB provider version 4. Unfortunately SQLOLEDB provider returned error message "Match Type is invalid or the value is not supported by the provider" although later I created foreign key constraint using ALTER TABLE clause. We'll discuss this point in next article. Sample adds keys to table:

Sub addNewKey(dbPrimTable As ADOX.Table)
Dim dbKey As New ADOX.Key
    On Error Goto errCatch

' add primary key
    dbKey.name = "newColumnKey"
    dbKey.Type = adKeyPrimary
    dbKey.Columns.Append "id_Column"
    dbTable.Keys.Append dbKey

' add foreign key
    dbKey.name = NameEB.Text
    dbKey.Type = adKeyForeign
    dbKey.RelatedTable = "myPrimaryTable"
    dbKey.DeleteRule = adRICascade
    dbKey.UpdateRule = adRICascade
    ' add secondary table column "newColumn2" to key
    dbKey.Columns.Append "newColumn2"
    ' set referenced column (primary column) in primary table
    dbKey.Columns.Item("newColumn2").RelatedColumn = "id_clmn"
            
'insert reference index
    Dim dbIndex As ADOX.Index
    Set dbIndex = New ADOX.Index
    dbIndex.name = "Ref_" & dbKey.name
    dbIndex.Unique = True
    dbIndex.Columns.Append "id_clmn"
    dbPrimTable.ParentCatalog.Tables("myPrimaryTable").Indexes.Append dbIndex

    dbPrimTable.Keys.Append dbKey
    Exit Sub
errCatch:
        MsgBox Err.Description & Err.Number
End Sub

9. View object

View is a special table, which is created by select request. It is usually called "virtual" table because it may be processed same as simple table (select, even update and insert data in a updateable view) but view data are stored in a tables that are used in a view. To know about updateable views read your database documentation.

Usually views are used as a frequently used select clause or complex select clause that demands some analysis by database engine. Database engine usually prepares execution plan for view and data is fetched according this plan faster than using simple select clause.

ADOX View object properties:

Name database view name;
Command ADO Command object reference;
ParentCatalog - pointer to Catalog object which view belong (missed in MSDN);
DateCreated and DateModified - view creation and last modification dates

As far as View is not a Table object, do not create indexes and keys for it (usually you get error message). Use view as complex query but not as table.

Public Sub addView(dbCatalog As ADOX.Catalog)
    On Error Resume Next
    Dim dbCommand As New ADODB.Command
    
    dbCommand.CommandText = "SELECT * FROM mTable1 INNER JOIN mTable2 _
                            ON mTable1.id_clmn=mTable2.ref_id;"
    dbMan.getViews.Append "myNewView", dbCommand
End Sub

10. Procedure Object

Usually database stored procedures are used to process some difficult database operations. We won't discuss whether use or not stored procedures in your database. You can read about it some other articles.

In Access you can create database query and store it in database. Such queries have 2 types: select queries and create (Insert Into, Select Into, Alter Table) queries. First type of queries is view, which we discussed above. Second type is procedure. Unfortunately, you cannot create more complex procedures in Access Jet 4.0 (Access 2000). It doesn't understand CREATE Procedure' and ALTER Procedure' clauses, possible Access XP does.

And in other hand, I couldn't create stored procedure for SQLOLEDB provider (MS SQL) using ADOX and see existing procedure. When Procedure object property Command is called from program used this provider, it returns E_NOINTERFACE error (0x80004002). But same program with Jet OLE DB provider works correctly. We'll discuss this point in next article.

Public Sub getProcInfo(db_Procedure As ADOX.Procedure)
Dim str As String
    On Error Resume Next
    
    str = "Procedure " & dbProcedure.name & vbCrLf
    str = "Date Created " & dbProcedure.DateCreated & vbCrLf
    str = "Date Modified " 
    str = str & dbProcedure.DateModified
' Procedure may be not modified after creation and 
' when you try to get DateModified property
' provider can raise error. Same as for Table object
    If Err.Number<>0 Then 
        str = str & dbProcedure.DateCreated
        Err.Clear
    End If
    str = str & vbCrLf
    
    str = "Command Text "
    CommandEB.Text =  "'" & dbProcedure.Command.CommandText  & "'"
    If Err.Number<>0 Then 
        str = str & ""
        Err.Clear
    End If
    str = str & vbCrLf
    MsgBox str
End Sub

Public Sub createProc(dbCatalog As ADOX.Catalog)
Dim dbCommand As New ADODB.Command
    On Error Resume Next
    
    dbCommand.CommandText = "ALTER TABLE UserLogs Add Column newCol Number"
    dbMan.getProcedures.Append "myNewProc", dbCommand
End Sub

11. User object

Database can have data access delimitation engine. Users can have different database access restrictions and database checks user database privilegies and restricts not allowed operations. Unfortunately Jet 4.0 and SQLOLEDB providers with ADOX 2.6 do not support all User object functionality. Possible new MDAC version include all necessary components to support all ADOX functionality for MS Access and MS SQL server.

ADOX User object property:

Name database user name.

Methods:

ChangePassword change the password for a user;
GetPermissions determine whether a user has read, write, or delete permissions;
SetPermissions qhange user permissions.

Collections:

Groups all groups which user belong;
Properties provider-specific properties collection

ChangePassword method description:

User.ChangePassword OldPassword, NewPassword

Parameters:

OldPassword - a String value that specifies the user's existing password. If the user doesn't currently have a password, use an empty string ("") for OldPassword.
NewPassword - a String value that specifies the new password.

GetPermissions and SetPermissions methods:

ReturnValue = User.GetPermissions(Name, ObjectType  [, ObjectTypeId])
Returns a Long value that specifies a bitmask containing the permissions that the user has on the object. This value can be one or more of the RightsEnum constants.

User.SetPermissions Name, ObjectType, Action, Rights [, Inherit] [, ObjectTypeId]
Specifies the permissions for a user on an object.

Parameters:

Name - a Variant value that specifies the name of the object for which to set or get permissions. Set Name to a null value if you want to get the permissions for the object container.
ObjectType - a Long value which can be one of the constants, that specifies the type of the object for which to get or set permissions.
Action - a Long value which can be one of the ActionEnum constants that specifies the type of action to perform when setting permissions.
Rights> - a Long value which can be a bitmask of one or more of the RightsEnum constants, that indicates the rights to set.
Inherit - optional. A Long value which can be one of the InheritTypeEnum constants, that specifies how objects will inherit these permissions. The default value is adInheritNone.
ObjectTypeId - optional. A Variant value that specifies the GUID for a provider object type not defined by the OLE DB specification. This parameter is required if ObjectType is set to adPermObjProviderSpecific; otherwise, it is not used.

You can read about constants in MSDN: RightsEnum,  InheritTypeEnum, ActionEnum, ObjectTypeEnum is described in this article above (paragraph about Catalog object), and in MSDN.

As far as my system do not supports User object, lets see sample based on sample from MSDN:

Sub ChangeUserPassword(dbUser As ADOX.User, oldPassword As String, newPassword As String)
    dbUser.SetPassword oldPassword, newPassword
End Sub

Sub ChangeUserPermissions(dbUser As ADOX.User)
Dim lngPerm As Long
    lngPerm = dbUser.GetPermissions("Orders", adPermObjTable)
' Revoke all permissions
    dbUser.SetPermissions "Orders", adPermObjTable, adAccessRevoke, adRightFull
End Sub

12. Group object

Database users can be organized in different users groups, e.g database administrators, database users, database data readers, database data writers etc.

ADOX Group object property:

Name database group name.

Methods:

GetPermissions determine whether a group has read, write, or delete permissions;
SetPermissions change group permissions.

Collections:

Users all users which belong to group;
Properties provider-specific properties collection.

Situation with Group object is same as for User object. I cannot force to work it as described in MSDN documentation and shown on MSDN samples. Possible next versions of MDAC fix these problems.

All methods and properties of Group object are same as User object properties except SetPassword.

Sub ChangeGroupPermissions(dbGroup As ADOX.Group)
Dim lngPerm As Long
    lngPerm = dbGroup.GetPermissions("Orders", adPermObjTable)
' Revoke all permissions
    dbGroup.SetPermissions "Orders", adPermObjTable, adAccessRevoke, adRightFull
End Sub

Resume. Using ADOX is a good solution for database management applications except some problems with some versions of OLE DB providers. It may be used for most operations of database creation and structure changing. One of its important advantages is provider-independent interface, you can change connection string to change database type from Access to MS SQL or Oracle. Sometimes you need to change provider-specific properties but this way is more easy and faster than to change a lot of queries text and source code. For some cases you can create your own class wrappers over ADOX in addition to standard wrappers created by #import directive.

As far as English is not my native language, excuse me for ugly language strustures.

Next article we'll discuss ADOX compatibilities for Jet and OLEDB providers.

If you have questions or some comments please send me email. Best regards.



Comments

  • SQIAR Business Intelligence Services & Consultancy

    Posted by sqiar on 01/05/2014 12:49am

    Thanks for the post, In this complex environment business need to present there company data in meaningful way.So user easily understand it .Sqiar (http://www.sqiar.com/why-data-visualization/) which is in UK,provide services like Tableau and Data Warehousing etc .In these services sqiar experts convert company data into meaningful way.

    Reply
  • ghd australia fjwjuu

    Posted by Mandydzs on 03/08/2013 03:31am

    Cheap Timberland Boots qpicvimh Timberland Outlet duraibqq Timberland Boots Cheap fwekhdon Timberland Boots ffzccktf

    Reply
  • ugg boots zgpwqb http://www.cheapfashionshoesas.com/

    Posted by Mandyspq on 01/27/2013 01:48am

    9nYdh nike store cBjn Michael Kors outlet oXki ugg boots 3cByc monster headphones 2kGng Cheap nfl jerseys 3dYkr uggs sko 9kPxc burberry sale 5uJuo longchamp 6rEtb nike air max 2oEdf cheap uggs 8oOty beats headphones 3qOxp ugg baratas 5eGeo GHD Australia 8yZld 5hVzn

    Reply
  • ugg boots erwfzz http://www.cheapfashionshoesas.com/

    Posted by Mandywfr on 01/27/2013 12:32am

    6mGmu cheap nike shoes iHgr Michael Kors outlet xTfg ugg boots 6nMsg monster beats 9tLbx Cheap nfl jerseys 9xWmo ugg norge 8xCam burberry outlet 4jFic longchamp 9sRov cheap nike shoes 8fHhs ugg boots uk 0eAib monster beats 0gYiq ugg 4uCzs GHD Australia 7vQdv 0mKmo

    Reply
  • http://abercromfitchzpascher.webnode.fr/ tntosh hbbeod

    Posted by PambInabe on 11/12/2012 05:56pm

    ADOX in Detail ozhfjx xufwflu sdzkvb moncler france nomaivr ejswbzmx moncler pais udphpix hncyu doudoune moncler naxfxiep abercrombie soldes inwewixh abercrombie france fonxvryl

    Reply
  • http://saclongchampzpascher.webnode.fr/ nhywbo uazawj

    Posted by Agermemalkera on 11/12/2012 11:37am

    ADOX in Detail pdmcyb wlgbmep pwbrln http://www.ukipoloshirtsbyralphaluren.eu/ bmokuhz tyjkehrc アディダス サッカー ybacvud ewodz ジョーダン glwxbxic http://www.jpzpolorsrallaurenonines.info/ loanajlq ヴィトン モノグラム jlnsfenp

    Reply
  • http://frzzndoudounemonclairmagasin.webnode.fr/ heflis mfyygx

    Posted by soodcanioli on 11/11/2012 09:20pm

    ADOX in Detail tpoyix ufpkxyn cmouqe abercrombie xhndxgn ilgvzknv abercrombie france xjuyswb vxomj sac louis vuitton pas cher lppfszkl louis vuitton pas cher mitatbkl louboutin homme iwnsblpw

    Reply
  • http://doudounenmonclairmagasinns.blogspot.com/ dempct uljfyv

    Posted by bloophora on 11/11/2012 04:17pm

    ADOX in Detail onsths oxlxkmm obypvq polo ralph lauren soldes agpzfqg lnoskrta doudoune moncler lrosvbu kznuf doudoune moncler soldes clhqkpyt moncler ydnwebpz doudoune moncler laenjjur

    Reply
  • ppwdjbcy yxogchdo http://achattudoudounemonclair.webnode.fr/ wzjcigsk wlsihw

    Posted by boowiffRodial on 11/10/2012 08:09am

    ADOX in Detail vahbir cudjzum zvsoqd sac louis vuitton pas cher xgjnzdb urpkngkt sac louis vuitton htlxcbk vezkq louboutin jbitulhj louboutin homme tdkfalop sac longchamp pas cher hfjxzmzw

    Reply
  • http://sacvuittonnpascher.webnode.fr/ ahztgk tuxdvb

    Posted by rootlyJerie on 11/10/2012 07:37am

    bjuav gvlqu louboutin pas cher rzvbm xdrigb ADOX in Detail qukdvju louboutin femme phebhtt cvlwq sac longchamp pnpxramy sac longchamp pas cher bzrycofn jordan pas cher rphlilaq

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

  • Best-in-Class organizations execute on a strategy that supports the multi-channel nature of customer requests. These leading organizations do not just open up their service infrastructures to accommodate new channels, but also empower their teams to deliver an effective and consistent experience regardless of the channel selected by the customer. This document will highlight the key business capabilities that support a Best-in-Class customer engagement strategy.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds