Modelgedreven ontwikkelstraat in .NET (5): Data Adapter

 
21 november 2008

Eerder schreef ik over verschillende aspecten voor het samenstellen van implementaties van domeinobjecten op basis van een UML model. Vervolgens heb ik gedemonstreerd hoe je op basis van ditzelfde model een database schema kan genereren dat hierbij aansluit. Ik zal in deze post ingaan op de ‘tussenlaag’; hoe lepel ik mijn objecten uit een database en hoe schrijf ik ze weg?

(N)Hibernate hoor ik nu veel mensen denken. Correct, dat zou prima werken. Toch kies ik ervoor om hier een eigen oplossing te geven. Deels om aan te geven dat O/R mapping helemaal niet moeilijk is, deels omdat (N)Hibernate niet in alle gevallen handig is.

Laten we starten met de manier van objecten opslaan in de database. Ik ga in deze post op een vrij recht-toe-recht-aan manier te werk; ik breid mijn bestaande domain classes uit met een aantal repository methoden. Ik kies voor de allerplatste implementatie, zie de Save() methode van de employee hieronder:

public Employee Save()
{
  OdbcCommand cmd = new OdbcCommand();
  string fieldpart = "id";
  string valuepart = "?";
  string setpart = "SET id=?";
  cmd.Parameters.
    AddWithValue(Guid.NewGuid().ToString(), this.Id);
  fieldpart += ",Name";
  valuepart += ",?";
  setpart += ",Name=?";
  cmd.Parameters.
    AddWithValue(
      Guid.NewGuid().ToString(), 
      (name == null) ? (object)DBNull.Value : (object)name);
  connection.Open();
 
  //Check if record exists
  OdbcCommand existscmd = new OdbcCommand();
  existscmd.Connection = connection;
  existscmd.CommandText = 
    "select count(*) from Employee where id='" + this.Id.ToString() + "'";
 
  //Exists? Update. Else insert.
  if (((int)existscmd.ExecuteScalar()) > 0)
  {
    string updexp = 
    "UPDATE Employee " + setpart + " WHERE id='" + this.Id.ToString() + "'";
    cmd.CommandText = updexp;
  }
  else
  {
    string insexp = 
      "INSERT INTO Employee(" + fieldpart + ") VALUES(" + valuepart + ")";
    cmd.CommandText = insexp;
  }
  cmd.Connection = connection;
  cmd.ExecuteNonQuery();
  connection.Close();
  return this;
}

Je kunt je iets voorstellen bij de template. Kwestie van loopen door alle classes en alle fields. Ik kies voor de retrieval van objecten uit de database een even zo platte find methode. Deze kan ik zelfs rechstreeks SQL expressies voeren om mijn objecten te vinden (tja, SQL is daar voor gemaakt, toch?):

public static List<Employee> Find(string expression, object[] parameters)
{
    OdbcCommand cmd = new OdbcCommand(expression, connection);
    List<Employee> items = new List<Employee>();
 
    foreach (object o in parameters)
    {
        cmd.Parameters.Add(new OdbcParameter(Guid.NewGuid().ToString(), o));
    }
    DataTable dt = new DataTable();
    OdbcDataAdapter da = new OdbcDataAdapter(cmd);
    da.Fill(dt);
 
    foreach (DataRow row in dt.Rows)
    {
        Employee instance = new Employee();
        instance.id = (Guid)row["id"];
        if (row["name"] != DBNull.Value)
            instance.name = (String)row["name"];
        items.Add(instance);
    }
    return items;
}

Wederom lijkt me duidelijk hoe dit werkt. Loopen door classes en fields en we kunnen de code voor deze vrij lijvige repositorymethodes gewoon genereren.

De template vind je hier.

Een heel interessant aspect van deze benadering vind ik persoonlijk het veel besproken onderwerp ‘lazy loading’. (N)Hibernate regelt dat voor jou op basis van veel reflectie en een proxy pattern waardoor er soms vreemde dingen met je objecten gebeuren. Door op deze manier met je storage en retrieval om te gaan – en tevens je domainclasses te genereren – ben je in staat om een simpele find in te voegen in bijvoorbeeld elke getter van een collection. Je kunt tevens iets doen met caching als je zou willen. Dit biedt natuurlijk best leuke mogelijkheden om eens over na te denken.

Je herinnert je dat ik met een centraal model en code generatie werk, dus niemand weerhoudt me om dit soort code te genereren in elke getter van de collections (hier het voorbeeld van de Activities lijst van Employee):

public BindingList<Activity> Activities
{
     get
     {
        if (activities == null)
            activities =
                new BindingList<Activity>(
                    Activity.
                    Find("select * from activity where employee=?", 
                        new object[] { this.id }));
		return this.activities;
     }
}

Werken met ?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Architectuur, Development

Tags: , , ,