Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / database / SQL-Server

Building a plug-in model to load VistaDB - Part II

4.20/5 (3 votes)
22 Jun 2010CPOL3 min read 12K  
Plugins are used in data applications to support more than one provider from a single codebase. The Provider Factories in ADO.NET is one such example, but you cannot work with the provider specific features when you use these factories since they only implement generic ADO.NET functions.

Plugins are used in data applications to support more than one provider from a single codebase. The Provider Factories in ADO.NET is one such example, but you cannot work with the provider specific features when you use these factories since they only implement generic ADO.NET functions. If you need to be able to use provider specific functions (like VistaDB’s DDA Pack routines), you have to load the provider somehow. In most cases, you put that logic and bindings into a separate assembly and load it when that ability is needed. By taking this approach a little further and building interfaces, you can abstract your logic to support more than one database provider using this model.

In this article, I will explain more of how to code works to use VistaDB in a plug-in model previously explained in building a plug in model for VistaDB - Part 1. The application used to demonstrate the plug-in model is a simple Windows Form that allows users to deposit, withdraw or check the balance of an account that is stored in either a VistaDB or SQL Server database.

We will implement the VistaDB custom plugins in this example. You could implement other plugins for any other database you wish to support through the same interface. In each case, these plugins are hard bound against the database provider, but the actual application has NO binding to the database directly. This is to allow it to run on machines where the database provider has not been installed.

Factory Classes in the Main Application

The following classes are all contained within the applications' main assembly.

Plug-in Factory

The plug-in factory is an abstract class that each custom plugin will need to inherit from to load from the singleton factory.

C#
public abstract class PluginFactory
{
  #region public members
  public abstract BankModel.Provider ProviderName { get; }
  #endregion

  #region public methods
  public abstract BankModel GetModel(string connectionString);
  #endregion
}

ProviderName will return the name of BankModel provider, VistaDB or SqlServer. The GetModel method takes a database connection string and returns the abstract BankModel for that provider.

Plug-in Factories Singleton

This singleton class is used to reflection load one of the BankModel concretes when asked for by the consumer.

C#
public static class PluginFactories
{
  static PluginFactory LoadFactory(BankModel.Provider provider)
  {
    System.Reflection.Assembly assembly;
    Type assemblyType;
    PluginFactory factory;

    switch (provider)
    {
      case BankModel.Provider.VistaDB:
        try
        {
          assembly = Assembly.LoadFrom
			(string.Format(@"{0}\VistaDBBankModel.dll",
            Directory.GetCurrentDirectory()));
        }
        catch
        {
          throw new Exception(string.Format(@"{0}\VistaDBBankModel.dll",
            Directory.GetCurrentDirectory()));
        }
        assemblyType = assembly.GetType("PluginSample.Plugin.BankModelPlugin");
        factory = Activator.CreateInstance(assemblyType) as PluginFactory;
        return factory;

      case BankModel.Provider.SqlServer:
        try
        {
          assembly = Assembly.LoadFrom(string.Format
			(@"{0}\SqlServerDataModel.dll",
            Directory.GetCurrentDirectory()));
        }
        catch
        {
          throw new Exception(string.Format(@"{0}\SqlServerDataModel.dll",
            Directory.GetCurrentDirectory()));
        }
        assemblyType = assembly.GetType("PluginSample.Plugin.BankModelPlugin");
        factory = Activator.CreateInstance(assemblyType) as PluginFactory;
        return factory;
          
        default:
        return null;
    }
  }

  public static PluginFactory GetFactory(BankModel.Provider provider)
  {
    return LoadFactory(provider);
  }
}

Each concrete implementation must be contained within its own assembly and be located in the applications running directory. Using reflection to load the assembly ensures that the hard bound reference is only used when asked for by the code. You do not need any hard bound references to the actual provider.

This means you can have a VistaDB plugin on your system, but if the engine is not found it will fail to load – rather than your entire application failing to load.

The Abstract BankModel class

This class is implemented by every provider using that provides concrete implementation code.

C#
public abstract class BankModel
{
  public enum Provider { VistaDB, SqlServer };
  public string Connection { get; set; }

  public BankModel(string connection)
  {
    Connection = connection;
  }

  public abstract bool Deposite(int accountNumber, int pinNumber, decimal amount);
  public abstract bool Withdrawl(int accountNumber, int pinNumber, decimal amount);
  public abstract decimal GetBalance(int accountNumber, int pinNumber);

  public abstract bool TestConnection();
}

The Plug-in Consumer

We will use a Windows Form for users to interact with the bank model by specifying which underlying provider they wish to use.

C#
public void Deposit(int AccountNumber, int PinNumber, decimal amount)
{
  string connection = "Data Source = C:\\VistaDBBank.vdb4";

  BankModel model = null;

  try
  {
    Plugin.PluginFactory factory = Plugin.PluginFactories.GetFactory(
      BankModel.Provider.VistaDB);

    model = factory.GetModel(connection);
  }
  catch (Exception e)
  {
    MessageBox.Show(string.Format("Failed to load provider {0}, ERROR: {1}",
      "VistaDB", e.Message));
    return;
  }

  try
  {
    model.TestConnection();
  }
  catch (Exception e)
  {
    MessageBox.Show(string.Format(
      "Connection failed, check connection string and try again, ERROR {0}",
      e.Message));
    return;
  }


  bool s = model.Deposit(AccountNumber, PinNumber, amount);

  if (s)
  {
    MessageBox.Show(string.Format("Deposite made, new balance [{0}]",
      model.GetBalance(AccountNumber,
      PinNumber)));
  }
  else
  {
    MessageBox.Show("Failed to make deposite, 
		check account information and try again.");
  }
}

In this code, I use the Factory singleton to load the VistaDB provider, then return the VistaDBBankModel from my factory by passing a VistaDB connection string. The VistaDBBankModel can now be used generically to execute the BankModel methods without knowing the underlying provider specifics. You could implement Maintenance, and Backup functions that are called periodically without knowing how each is performed in the application itself.

Plug-in Assembly Per Provider

Each provider implementation will need to contain a concrete Factory and BankModel. Each of these assemblies can be hard bound against the provider because they will only be loaded by the consumer. If the dependencies don’t exist, only the plug in will fail to load.

VistaDB Factory

C#
public class BankModelPlugin : PluginFactory
{
  #region public members
  public override BankModel.Provider ProviderName
  {
    get { return BankModel.Provider.VistaDB; }
  }
  #endregion

  #region public methods
  public override BankModel GetModel(string connectionString)
  {
    return new VistaDB.BankModel.VistaDBBankModel(connectionString);
  }
  #endregion
}

This class implements the PluginFactory class for the VistaDB provider.

VistaDB Bank Model

C#
public override bool Deposit(int accountNumber, int pinNumber, decimal amount)
{
  using (VistaDBConnection connection = new VistaDBConnection())
  {
    connection.ConnectionString = Connection;

    try
    {
      connection.Open();

      using (VistaDBCommand command = new VistaDBCommand())
      {
        command.Connection = connection;

        StringBuilder sb = new StringBuilder();
        sb.Append("UPDATE Accounts ");
        sb.Append("SET balance = balance + @balance ");
        sb.Append("SELECT balance FROM Accounts ");
        sb.Append("WHERE accountnumber = @accountNumber ");
        sb.Append("AND pinnumber = @pinNumber ");

        command.CommandText = sb.ToString();

        command.Parameters.AddWithValue("@balance", amount);
        command.Parameters.AddWithValue("@accountNumber", accountNumber);
        command.Parameters.AddWithValue("@pinNumber", pinNumber);

        decimal s = Convert.ToDecimal(command.ExecuteScalar());

        if (s == null)
          return false;
        else
          return true;
      }
    }
    catch (Exception e)
    {
      throw e;
    }
  }
}

This is a concrete implementation of the BankModel Deposit method that is using strongly typed VistaDB classes. At this point, since I am hard bound against VistaDB, the DDA interface could also be used for many of the database operations. That is one thing ADO.NET provider factories cannot do.

Summary

This has been an example of how to use VistaDB with other providers in a plug-in model where only the plug-in assemblies are hard bound to their provider. You may also want to read the first part of the plug-in database provider article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)