by Andreas

Generisk databaseklient

Nylig oppstod et behov for å hente ut informasjon fra et sett med .DBF-filer som stammet fra en kundes eldre backendsystem. Dette dreide seg om en databasestrukur hvor tabellene var lagret i separate filer, og sannsynligvis lå det nok en relasjonsoversikt godt gjemt inne i det gamle backendsystemet et sted. Kort oppsummert: Kunden trengte en daglig dump av disse dataene, i et format som passet et nyere 3-partssystem.

Kjappeste vei til mål var å lage en generisk OLEDB-klient som kunne hente ut det jeg trengte fra de tabellene jeg var interessert i. Gjenbruk var derfor av høy pri, og løsningen jeg kom frem til er noe som lett kan pakkes i en assembly og brukes mot nyere eller eldre databasesystemer når behovene er enkle og tiden er knapp.

Logging og utvidet feilhåndtering er fjernet for å øke lesbarhet. OleDbConnection kan byttes ut med for eksempel SqlConnection om måldatabasen er SQL Server:

OLEDBClient.cs

public class OLEDBClient
{
private OleDbConnection _conn = null;
private string _connectionString = null;

public OLEDBClient(string connectionString)
{
_connectionString = connectionString;
}

private void connect()
{
// throws exception if connection fails
if (_conn == null)
{
_conn = new OleDbConnection(_connectionString);
}
}

public bool testConnection()
{
try
{
if (_conn == null)
connect();

return true;
}
catch (Exception e)
{
_log.Fatal("testConnection() error: '" + e.Message + "'");
return false;
}
}

public DataSet runGenericSQL(string sql)
{
try
{
if (_conn == null)
connect();

OleDbDataAdapter adapter = new OleDbDataAdapter(sql, _conn);
DataSet resultSet = new DataSet();
adapter.Fill(resultSet);
return resultSet;

}
catch (Exception e)
{
return null;
}
}
}

Denne klassen kan da kalles med en connection string som peker på en .dbf, i dette formatet:
“Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\TEST\DB.DBF”:

SomeApp.cs

private void GetSomeDataFromOleDB()
{
OLEDBClient client = new OLEDBClient(_DBConnectionString);

// ** SQL QUERY DEFINED HERE ** //
DataSet result = client.runGenericSQL("SELECT * FROM someTable WHERE someField IS NOT NULL");

if (result == null)
throw new Exception("Unable to retrieve data from database connection '" + _DBConnectionString + "'");

// loop through all records
foreach (DataRow row in result.Tables[0].Rows)
{
if (row["someField"] != null && !row["someField"].Equals(""))
{
// do something

}

......

  • Petter

    Hei der,

    det er noe i koden din jeg er usikker på om er riktig eller nødvendig.
    La oss ekspandere runGenericSQL og putte inn noen kommentarer:

    public DataSet runGenericSQL(string sql)
    {
    try
    {
    // første gangs kjøring, _conn er null
    if (_conn == null)
    {
    // _conn er fremdeles null
    if (_conn == null)
    {
    // _conn blir initsiert
    _conn = new OleDbConnection(_connectionString);
    }

    // ved første gangs kjøring vil state være Closed
    if (_conn.State != System.Data.ConnectionState.Open)
    {
    // så _conn åpnes og _conn.State blir satt til Open
    _conn.Open();
    }
    }

    // kjør spørring
    OleDbDataAdapter adapter = new OleDbDataAdapter(sql, _conn);
    DataSet resultSet = new DataSet();
    adapter.Fill(resultSet);
    return resultSet;

    }
    catch (Exception e)
    {
    return null;
    }
    finally
    {
    // _conn er ulink null
    if (_conn != null)
    {
    // så _conn lukkes og _conn.State blir Closed
    _conn.Close();
    }
    }
    }

    Så kjører vi runGenericSQL en gang til:

    public DataSet runGenericSQL(string sql)
    {
    try
    {
    // ved andre gangs kjøring er _conn ulik null, så alt dette droppes
    if (_conn == null)
    if (_conn == null)
    {
    _conn = new OleDbConnection(_connectionString);
    }

    if (_conn.State != System.Data.ConnectionState.Open)
    {
    _conn.Open();
    }
    }

    // kjør spørring, men denne gangen med _conn.State satt til Closed(!)
    OleDbDataAdapter adapter = new OleDbDataAdapter(sql, _conn);
    DataSet resultSet = new DataSet();
    adapter.Fill(resultSet);
    return resultSet;

    }
    catch (Exception e)
    {
    return null;
    }
    finally
    {
    if (_conn != null)
    {
    // vi lukker en allerede lukket _conn
    _conn.Close();
    }
    }
    }

    Selv om ting varierer over tid her, og kanskje tilsynelatende ikke burde ha virket, blir vi reddet av
    DataAdapter.Fill.

    Hentet fra spec’en:
    “The connection object associated with the SELECT statement must be valid, but it does not need to be open. If the connection is closed before Fill is called, it is opened to retrieve data, then closed. If the connection is open before Fill is called, it remains open.”

  • http://blog.degree.no/bloggere/ Andreas

    Jepp,

    jeg ser hvor du vil. Det er ikke nødvendig å kjøre .Open() og .Close() så lenge du bruker DataAdapter som selv tar seg av denne biten. Det ble liggende igjen fra en tidligere variant der jeg ikke brukte OleDbDataAdapter (som er mye lettere), men heller noe som jeg på det tidspunktet syntes var lurt! :)

    I fix – takk for tipset!