C# singleton pro přístup k více mySQL databázím
Petr Wiedemann, 4. duben 2009
Při psaní aplikací v C# jsem se ze začátku setkával s problémem, jak přistupovat k mySQL databázi z více tříd v programu. V C# neexistují globální proměnné, jako je tomu například v C++. Pokud máme v aplikaci například více oken odvozených od třídy Form a každé z nich musí komunikovat s databází, nemůžeme se spolehnout na globální instanci MySqlConnection, protože ve stylu C++ jí nevytvoříme. Jednou z možností, jak se spojit s databází je vytvořit si k ní nové spojení při každém otevření okna. Tento způsob ale asi nebude moc efektivní, přeci jen inicializace spojení k serveru stojí nějaký čas a asi je zbytečné, když bude mít jedna aplikace aktivní 3 spojení s databází. Pro tento problém si můžeme vytvořit singleton třídu, která bude uchovávat pole všech použitých MySqlConnection objektů.
Vytvoříme si tedy třídu ConnectionPool s následujícími vlastnostmi:
- poskytuje statickou metodu Instance, kterou stačí zavolat jen jednou před 1. použití a která vytvoří instanci samotné třídy
- nabízí metodu GetConnection, kterou získáme objekt třídy MySqlConnection z pole
- metodou AddConnection přidáme do pole nové spojení k databázi
Třída ConnectionPool vnitřně pracuje s polem tříd SingleConnection, která si udržuje objekt MySqlConnection a která sama zajišťuje spojení s databázovým serverem. Při neúspěchu vrací zpět informaci o chybě Message z vyjímky MySqlException.
Nejprve tedy vytvoříme třídu SingleConnection, se kterou budeme následně pracovat. Obsahuje pouze konstruktor, vlastnost IsConnectionOpen a 2 proměnné. Proměnné, které budeme potřebovat jsou objekty tříd MySqlConnection, který udržuje spojení k mySQL a String který obsahuje popis chyby, pokud selže volání metody Open třídy MySqlConnection. Konstruktoru předáváme adresu mySQL serveru, jméno uživatele, heslo, název databáze, příkaz, který se má provést po úspěšném připojení k serveru a případný suffix, který se připojí ke ConnectionStringu.
Implementace třídy SingleConnection
Parametry konstruktoru dbInitCommand a dbConnectionStringSuffix si zaslouží trochu vysvětlení.
- dbInitCommand se provede při úspěšném spojení s databází. Využít se může například pro určení znakové sady klienta. Například set names cp1250;
- dbConnectionStringSuffix se zase připojí ke ConnectionStringu ještě před voláním metody Open třídy MySqlConnection. Například CharSet=latin2;
Výpis třídy SingleConnection:
class SingleConnection
{
public MySqlConnection Connection = null;
public String ConnectionErrorString = "";
public SingleConnection(String dbServer,
String dbUser, String dbPassword, String dbDatabase,
String dbInitCommand, String dbConnectionStringSuffix)
{
if (IsConnectionOpen)
Connection.Close();
ConnectionErrorString = "";
String connStr = String.Format("server={0}; user id={1}; password={2}; database={3}; pooling=false;",
dbServer, dbUser, dbPassword, dbDatabase);
connStr += dbConnectionStringSuffix;
try
{
Connection = new MySqlConnection(connStr);
Connection.Open();
if (dbInitCommand.Length > 0)
{
MySqlCommand cmdIniCP = new MySqlCommand(dbInitCommand, Connection);
cmdIniCP.ExecuteNonQuery();
}
}
catch (MySqlException ex)
{
ConnectionErrorString = ex.Message;
if (Connection != null)
{
Connection.Close();
Connection = null;
}
}
}
public bool IsConnectionOpen
{
get
{
if (Connection == null)
return false;
if (Connection.State == System.Data.ConnectionState.Open)
return true;
else
return false;
}
}
}
Vlastnost IsConnectionOpen pouze zkontroluje, jestli existuje objekt MySqlConnection a jestli je jeho stav roven System.Data.ConnectionState.Open. Je následně využitá třídou ConnectionPool.
Implementace třídy ConnectionPool
ConnectionPool udržuje seznam aktivních spojení k databázím v poli Connections, které je tvořeno třídou Dictionary.
static Dictionary<String, SingleConnection> Connections = new Dictionary<String,
SingleConnection>();
Před 1. použitím třídy musíme zavolat její metodu Instance, která vytvoří novou instanci ConnectionPool ale pouze v případě, že ještě neexistuje. Všechny metody třídy ConnectionPool jsou statické. Mimo Instance máme ještě k dispozici metodu GetConnection, která vrací objekt typu MySqlConnection z pole Connections. Další dostupnou metodou je AddConnection, která zajistí vytvoření nového spojení k databázi, pokud ještě neexistuje.
Výpis třídy ConnectionPool:
public class ConnectionPool
{
static readonly object padlock = new object();
static ConnectionPool instance = null;
static Dictionary<String, SingleConnection> Connections = new Dictionary<String, SingleConnection>();
// jedina instance tridy ConnectionPool spolecna celemu programu
public static ConnectionPool Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new ConnectionPool();
}
return instance;
}
}
}
// konstruktor tridy volany pri 1. pouziti vlastnosti Instance
private ConnectionPool()
{
// odstraneni vsech spojeni na databazi v poli
Connections.Clear();
}
// escape uvozovek a stredniku
static public String EscapeData(String srcStr)
{
String retStr = srcStr.Trim();
retStr = retStr.Replace("\\", "\\\\");
retStr = retStr.Replace("'", "\\'");
retStr = retStr.Replace("\"", "\\\"");
return retStr;
}
// vraci MySqlConnection urciteho spojeni
static public MySqlConnection GetConnection(String ConnectionName)
{
MySqlConnection conn = null;
try
{
conn = Connections[ConnectionName].Connection;
}
catch (KeyNotFoundException)
{
return null;
}
return conn;
}
// vytvori nove spojeni s mySQL v poli
static public MySqlConnection AddConnection(String ConnectionName,
String ConnectionServer, String ConnectionUser,
String ConnectionPassword, String ConnectionDatabase,
String ConnectionInitCommand, String ConnectionConnectionStringSuffix,
out String ErrorString)
{
ErrorString = "";
// test, jestli toto spojeni jiz existuje
MySqlConnection conn = GetConnection(ConnectionName);
if (conn == null)
{
// vytvoreni noveho
SingleConnection singConn = new SingleConnection(ConnectionServer,
ConnectionUser, ConnectionPassword, ConnectionDatabase,
ConnectionInitCommand, ConnectionConnectionStringSuffix);
// vyskytla se chyba?
if (singConn.ConnectionErrorString.Length > 0)
{
// ulozeni popisu chyby
ErrorString = singConn.ConnectionErrorString;
// funkce vraci null (nastaveno vysledkem volani funkce GetConnection(ConnectionName))
}
else
{
// pridani noveho spojeni do pole
Connections.Add(ConnectionName, singConn);
conn = GetConnection(ConnectionName);
}
}
return conn;
}
}
Použití v aplikaci
Mějme 2 WinFrom třídy Form1 a Form2. Form1 je hlavní okno aplikace a Form2 je z ní volán. Celá aplikace potřebuje spojení ke 2 databázím. Ve Form1 zavoláme při startu metodu Instance a Connect2Databases.
using Wiedemann;
public class Form1 : Form
{
private ConnectionPool ConnectionPoolInstance = ConnectionPool.Instance;
// konstruktor tridy
public Form1()
{
InitializeComponent();
if (!Connect2Databases())
{
throw new Wiedemann.ConnectionFailedException();
}
LoadSomeRecordsFromDatabase();
}
// spojeni k databazim
bool Connect2Databases()
{
String ErrorString;
MySqlConnection conn1 = ConnectionPool.AddConnection("spojeni_k_databazi_1",
"server1", "uzivatel1",
"heslo1", "databaze1",
"", "CharSet=latin2;",
out ErrorString);
if (ErrorString.Length > 0)
{
MessageBox.Show(ErrorString);
return false;
}
MySqlConnection conn2 = ConnectionPool.AddConnection("spojeni_k_databazi_2",
"server2", "uzivatel2",
"heslo2", "databaze2",
"", "",
out ErrorString);
if (ErrorString.Length > 0)
{
MessageBox.Show(ErrorString);
return false;
}
return true;
}
// nacteni zaznamu z databaze
void LoadSomeRecordsFromDatabase()
{
MySqlConnection connection = ConnectionPool.GetConnection("spojeni_k_databazi_1");
if (connection != null)
{
String query = "select bool_column, string_column from table";
MySqlCommand commLoad = new MySqlCommand(query, connection);
MySqlDataReader reader = commLoad.ExecuteReader();
while (reader.Read())
{
if (reader.GetBoolean(0))
MessageBox.Show(reader.GetString(1));
}
reader.Close();
}
}
// funkce volana pri stisku tlacitka buttonForm2, ktera otevre nasi Form2
private void buttonForm2_Click(object sender, EventArgs e)
{
using (Form2 form2 = new Form2())
{
form2.ShowDialog(this);
}
}
}
V naší třídě Form2 již Instance volat nemusíme. Následující příklad načte data z druhé databáze.
using Wiedemann;
public class Form2 : Form
{
// konstruktor tridy
public Form2()
{
InitializeComponent();
LoadOtherRecordsFromDatabase();
}
// nacteni zaznamu z druhe databaze
void LoadOtherRecordsFromDatabase()
{
MySqlConnection connection = ConnectionPool.GetConnection("spojeni_k_databazi_2");
if (connection != null)
{
String query = "select bool_column, string_column from other_table";
MySqlCommand commLoad = new MySqlCommand(query, connection);
MySqlDataReader reader = commLoad.ExecuteReader();
while (reader.Read())
{
if (reader.GetBoolean(0))
MessageBox.Show(reader.GetString(1));
}
reader.Close();
}
}
}
Zdrojový soubor třídy: