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.
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.
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.
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.
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
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
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.