|
|
Forretningsobjekter
Hvis du har prøvet at lavet et forretningslag til dine små ASP.NET projekter, har du sikkert også bokset med hvordan det skulle skrues sammen så det blev overkommeligt at arbejde med, når du skulle lave nye klasser der kan gemme sine data i en database.
Personligt har jeg arbejdet med løsninger som benyttede sig af interfaces. I disse løsninger arbejdes der med at forretningslag og databaselag chatter lidt sammen om at starte og sætte CRUD-operationer (Create, Read, Update og Delete) igang og sende det frem og tilbage imellem forretningsobjektet og databaselaget. Denne metode fungerer, men er noget besværlig at arbejde med i opbygningsfasen og man får ikke separeret forretnings- og datalagene ordentligt. Derfor har jeg eksperimenteret lidt med en letvægtsmetode til at opbygge forretningsobjekter og synes egentlig det fungerer nogenlunde. Der er stadig mange ting som kan forbedres og man bliver låst lidt, men jeg vil mene den kan klare en større procentdel af de CRUD-handlinger der udføres i et objekthierarki til et website, så der er lidt mere tid til at bokse med de rigtige problemer...
Den grundlæggende idé er, at alle CRUD-operationer omstilles af connection-setups og -teardowns, som i mine tilfælde typisk er trivielle. Derfor implementeres der en connection-wrapper, som sørger for at dette bliver gjort og kalder så videre til den CRUD-handling, der nu skal udføres. Til dette formål defineres en delegate, connection-wrapperen kan tage som input. Alt dette kan lægges i en superklasse, som tillader at selve CRUD-funktionerne nedarves og implementeres, mens infrastrukturen i forretningsobjektets databaseadgang håndteres af superklassen. Det er klart at denne infrastruktur ikke adskiller forretningslaget og databaselaget, men det gør håndteringen af forretningsobjekterne mere overkommelig.
// signaturen for den funktionsreference som benyttes at connectionwrapperen
// til at kalde videre til den ønskede CRUD-funktion.
public delegate bool ConnectionWrapperCallback(OleDbConnection cn);
// klassen der implementerer den grundlæggende infrastruktur for
// forretningsobjekters måde at gemme sig selv i en database.
public class Forretningsobjekt
{
public Forretningsobjekt()
{
}
protected bool ConnectionWrapper(ConnectionWrapperCallback callback)
{
string connStr = ConfigurationSettings.AppSettings["connection"];
using(OleDbConnection cn = new OleDbConnection(connStr))
{
try
{
cn.Open();
return callback(cn);
}
catch(Exception x)
{
}
finally
{
if(cn.State != ConnectionState.Closed)
cn.Close();
}
}
}
public bool Hent()
{
return ConnectionWrapper(new ConnectionWrapperCallback(HentData));
}
public bool Gem()
{
return ConnectionWrapper(new ConnectionWrapperCallback(GemData));
}
public bool Slet()
{
return ConnectionWrapper(new ConnectionWrapperCallback(SletData));
}
protected virtual bool HentData(OleDbConneciton cn)
{
return true;
}
protected virtual bool GemData(OleDbConneciton cn)
{
return true;
}
protected virtual bool SletData(OleDbConneciton cn)
{
return true;
}
}
Et forretningsobjekt kunne derefter se således ud:
public class Person : Forretningsobjekt
{
private int id = 0;
private string fornavn = "";
private string efternavn = "";
public Person() { }
public Person(int id)
{
Id = id;
Hent();
}
public Person(OleDbDataReader dr)
{
Init(dr);
}
public void Init(OleDbDataReader dr)
{
id = Convert.ToInt32(dr["id"]);
fornavn = Convert.ToString(dr["fornavn"]);
efternavn = Convert.ToString(dr["efternavn"]);
}
protected override bool HentData(OleDbConnection cn)
{
string sql = "SELECT * FROM person WHERE id = @id";
OleDbCommand cmd = cn.CreateCommand();
cmd.Parameters.Add("@id", Id);
using(OleDbDataReader dr = cmd.ExecuteReader())
{
try
{
dr.Read();
Init(dr);
}
catch(Exception x)
{
// noget fejlhåndtering bør indsættes her...
}
finally
{
if(!dr.IsClosed)
dr.Close();
}
}
return true;
}
protected override bool GemData(OleDbConnection cn)
{
string sql = "";
if(Id == 0)
sql = @"INSERT INTO person
(fornavn, efternavn)
VALUES
(@fornavn, @efternavn)";
else
sql = @"UPDATE person
SET fornavn = @fornavn,
efternavn = @efternavn
WHERE id = @id";
OleDbCommand cmd = new OleDbCommand(sql, cn);
cmd.Parameters.Add("@fornavn", Fornavn);
cmd.Parameters.Add("@efternavn", Efternavn);
if(Id != 0)
cmd.Parameters.Add("@id", Id);
cmd.ExecuteNonQuery();
if(Id == 0)
{
// hent den nyoprettede person's id, så objektet kan
// gemmes/opdateres efterfølgende
cmd = new OleDbCommand("SELECT @@IDENTITY", cn);
object oid = cmd.ExecuteScalar();
if(oid != null)
Id = (int)oid;
else
throw new Exception(
"Opret person",
"Det var ikke muligt at hente identifikationen af " +
"personen i databasen efter oprettelsen.");
}
return true;
}
protected override bool SletData(OleDbConnection cn)
{
string sql = "DELETE FROM person WHERE id = @id";
OleDbCommand cmd = new OleDbCommand(sql, cn);
cmd.Parameters.Add("@id", Id);
cmd.ExecuteNonQuery();
return true;
}
public int Id { get { return id; } set { id = value; } }
public string Fornavn { get { return fornavn; } set { fornavn = value; } }
public string Efternavn { get { return efternavn; } set { efternavn = value; } }
}
Der er ingen tvivl om at der mangler mange ting og at mange ting kunne gøres mere generiske i ovenstående, men med den illustrerede model, kan man i det mindste slippe for at gentage sig selv alt for meget...
|