Resource Descriptor

Summary

Purpose

Isolates platform- and data-source-dependent behavior within a single component. A resource descriptor exposes platform and datasource specifics as generic logical operations. This allows the majority of data access code to remain independent of its physical environment.

Scenario

Database access code almost always has to address ADO.NET provider-dependent issues on a regular basis. For example, while SQL statements are mostly portable among database platforms, many products extend SQL syntax to provide additional features and optimizations that the standard does not support. As a result, data access code that must support different database platforms often need the ability to choose among different SQL statements that represent the same logical data access operation. As another example, many databases implement their own native errors and codes and applications deal with these errors as ADO.NET provide-generated exceptions. As a result, those applications that filter/depend on certain error codes/messages to make decisions need to handle different set of error codes for each database platform.

A naive/quick-fix-do-it-later solution would be to pepper data access code with checks for each supported database platform. A Resource Descriptor describes a way to customize environment-dependent features and operations within an isolated environment. How a resource descriptor accomplishes this is presented in the next section.

Structure & UML

The following diagram illustrates the static structure of the Resource Descriptor:

The IResourceDescriptor interface defines operations for any application code that changes depending on the runtime- or database-environment. These operations can retrieve attributes or implement functional behavior. ConcreteResourceDescriptor implements IResourceDescriptor in terms of a specific environment or database product. You write client code in terms of the IResourceDescriptor interface but interact directly with a ConcreteResourceDescriptor instance.

Example

public interface IResourceDescriptor
{
    string GetProductSQL();
    string GetCustomerSQL();
    string GetPricesSQL();
}

public class SQLServerResourceDescriptor : IResourceDescriptor
{
    // IResourceDescriptor implementation
    public string GetProductSQL()
    {
        return " ... ";
    }
    public string GetCustomerSQL()
    {
        return " ... ";
    }
    public string GetPricesSQL()
    {
        return " ... ";
    }
}


public class OracleResourceDescriptor : IResourceDescriptor
{
    // IResourceDescriptor implementation
    public string GetProductSQL()
    {
        return " ... ";
    }
    public string GetCustomerSQL()
    {
        return " ... ";
    }
    public string GetPricesSQL()
    {
        return " ... ";
    }
}

public class DescriptorMap
{
    // Data members
   
static private System.Collections.Hashtable m_htDescirptorNameToInstance = new Hashtable();

    // Public interface
   
public static void RegisterDescriptor( IResourceDescriptor desc, string strName )
    {
        if (!m_htDescirptorNameToInstance.Contains( strName ))
            m_htDescirptorNameToInstance.Add( strName, desc );
    }

    public static IResourceDescriptor ResolveDescriptor( string strName )
    {
        if (m_htDescirptorNameToInstance.Contains( strName ))
            return (IResourceDescriptor)m_htDescirptorNameToInstance[ strName ];
        else
            return null;
    }
}

class Client
{
    // Public interface
   
public void Initialize()
    {
        DescriptorMap.RegisterDescriptor( new SQLServerResourceDescriptor(), "SQLServer" );
        DescriptorMap.RegisterDescriptor( new OracleResourceDescriptor(), "Oracle" );
    }

    public void MoveDataFromWarehouseToTargetDB()
    {
        // Get SQL for target database
        IResourceDescriptor desc = DescriptorMap.ResolveDescriptor( "SQLServer" );

        // Import data into Customer table
       
string strCustomerImportSQL = desc.GetCustomerSQL();
        ...

        string strProductImportSQL = desc.GetProductSQL();
        ...
   
}
}

// Main application
Client ob = new Client();
ob.Initialize();
ob.MoveDataFromWarehouseToTargetDB

Applicability

Use this pattern when:

Strategies / Variants

Your data access code will have to interact with a particular IResourceDescriptor implementation at runtime. It is best to provide an environment-neutral mechanism that resolves which particular IResourceDescriptor implementation to use based on some contextual information such as database connection or data source representation. Here are some alternatives:

public static IResourceDescriptor ResolveDescriptor( eDatabasePlatofrom db )
{
    switch (db )
    {
        case eDatabasePlatform.SQLSERVER:
            return new SqlServerResDescriptor();
            break;
        case eDatabasePlatform.ORACLE:
            return new OracleResDescriptor();
            break;
        default:
            throw new InvalidArgumentExceptions( "There is no descriptor for platform " + db.ToString() );
    }
}

public void MoveDataFromSourceDBToTargetDB( string strSourceTableName, string strTargetTableName, ResourceDescriptor desc) { ... }

Benefits

Related Patterns