Data Access Layer (DAL) og enkelt oppsett av objekter mot database med C#

Det er mye prat om arkitektur, data access layer (DAL), business logic layer (BLL) osv. Men hva er det egentlig og hvordan kommer man i gang. I denne artikkelen får du et enkelt eksempel på hvordan du kan sette opp din egen håndtering av både data access og logikk.

Grunnlegende oppsett av objekter

Når man jobber med webløsninger er det en ting som går igjen svært ofte. Man har en tabell i en database og disse dataene vil man oppdatere og presentere. Det er faktisk så banalt enkelt i mange tilfeller at man bare trenger å lese, skrive og endre data i databasen. Svært mange utviklere skriver mange klasser og gjør ting langt vanskeligere og mer tungvindt enn strengt tatt nødvendig. Ja, det skal være skalerbart, men til hvilke pris?

Her er et eksempel på et oppsett jeg har brukt en god stund og som fungerer til de aller fleste webapplikasjoner. Oppsettet inneholder 3 klasser pr tabell og disse klassene har hver sin oppgave.

Object (User.cs)

Selve tabellen har noen kolonner og den samlingen med kolonner utgjør et objekt. For enkelhets skyld kan vi si at vi har en tabell med 3 kolonner: BrukerIdNavn og Email. For å gjøre dette skikkelig lager vi dette på engelsk og vil dermed ha følgende navn på kolonnene: UserIdName og Email

I dette eksempelet har vi altså en tabell med 3 kolonner som utgjør objektet. Dvs at vi i denne klassen trenger 3 properties som ivaretar disse 3 verdiene:

using System;

namespace Serverside.Examples
{
    public class User
    {
        public User()
        { 
        }

        public User(string name, string email)
        {
            _name = name;
            _email = email;
        }

        private int _userId;
        public int UserId
        {
            get { return _userId; }
            set { _userId = value; }
        }

        private string  _name;
        public string  Name
        {
            get { return _name; }
            set { _name = value; }
        }

        private string _email;
        public string Email
        {
            get { return _email; }
            set { _email = value; }
        }
    }
}

Collection (UserCollection.cs)

Dette er en klasse som skal ivareta samlinger av objektet. Brukes når man skal ha ut flere objekter samtidig. Man kunne selvsagt brukt en generell collection for å gjøre dete, men det er greit å ha en egen collection som kun ivaretar angitte objekter samtidig som man da har mulighet til å legge til egne metoder i selve collection.

using System;
using System.Collections;

namespace Serverside.Examples
{
    [Serializable()]
    public class UserCollection : CollectionBase
    {
        public UserCollection()
        {
        }

        public void Add(User user)
        {
            base.List.Add(user);
        }

        public void Remove(User user)
        {
            base.List.Remove(user);
        }

        public void Sort(IComparer comparer)
        {
            ArrayList list = ArrayList.Adapter(base.List);
            list.Sort(0, list.Count, comparer);
        }

        public User this[int index]
        {
            get
            {
                return (User)(base.List[index]);
            }
            set
            {
                base.List[index] = value;
            }
        }
    }
}

Handler (UserHandler.cs)

Denne klassen håndterer databasekoblinger og caching. Her skal vi legge inn metoder/funksjoner som henter, endrer, lagrer data osv.

using System;
using System.Data.SqlClient;

namespace Serverside.Examples
{
    public class UserHandler
    {
        public User GetUser(int userId)
        {
            User user = new User();

            SqlConnection conn = new SqlConnection("Server=.....");
            SqlCommand cmd = new SqlCommand("spGetUser", conn);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.AddWithValue("@UserId", userId);

            conn.Open();
            SqlDataReader dr = cmd.ExecuteReader();
            while (dr.Read())
            {
                user.UserId = Convert.ToInt32(dr["UserId"]);
                user.Name = dr["Name"].ToString();
                user.Email = dr["Email"].ToString();
            }
            conn.Close();

            return user;
        }
        
        public UserCollection GetUsers(string name)
        {
            UserCollection userCollection = new UserCollection();
            // Kode som henter brukere med angitt navn
            return userCollection;
        }

        public UserCollection GetUsers(System.Net.Mail.MailAddress email)
        {
            UserCollection userCollection = new UserCollection();
            // Kode som henter brukere med angitt emailadresse
            return userCollection;
        }

        public UserCollection GetUsers()
        {
            UserCollection userCollection = new UserCollection();
            // Kode som henter brukere fra databasen.
            return userCollection;
        }

        public User AddUser(User user)
        {
            // Kode som lagrer ny bruker i databasen.
            // Vi vil gjerne ha ut UserId for den nylig lagrede brukeren
            // slik at vi kan returnere et komplett objekt
            return user;
        }

        public void UpdateUser(User user)
        { 
            // kode som endrer brukeren
        }
    }
}

Da har man et bra grunnlag for å utvikle et bra brukersystem. Disse 3 klassene for hver tabell i databasen (med noen unntak naturligvis) og man kan da enkelt gjøre de databaseoperasjoner man trenger.

    // Hente ut en bruker
    User user = new UserHandler().GetUser(1);
    this.lblName.Text = user.Name;
    this.lblEmail.Text = user.Email;
    
    // Binde brukere til en Repeater
    this.repUsers.DataSource = new UserHandler().GetUsers();
    this.repUsers.DataBind();
    
    // Lagre en ny bruker
    User user = new User();
    user.Name = this.txtName.Text;
    user.Email = this.txtEmail.Text;
    user = new UserHandler().AddUser(user);
    
    // Oppdatere brukerdata
    User user = new UserHandler.GetUser(1);
    user.Name = "nytt navn";
    new UserHandler().UpdateUser(user);

Veien videre

Det er en rekke ting man kan gjøre for å utvide disse 3 klassene til å gjøre mer enn i dette eksempelet. Noen av tingene man kan kikke på: For å utvikle dette videre kan man Husk å bruke caching i UserHandler klassen slik at man ikke henter data oftere enn nødvendig.

  • Legge inn støtte for paging (se artikkelen Paging i SQL med stored procedure) slik at man kan hente ut brukere litt og litt.
  • Legge inn caching i UserHandler slik at man ikke stresser database unødvendig mye
  • Lage funksjonalitet for å sortere dataene man henter fra databasen
  • Lage en egen CollectionBase som hpndterer sortering av data etter at man har hentet dataene fra databasen
  • Og selvfølgelig legge til de metoder og funksjoner man trenger i sin UserHandler
  • Legge inn feilhåndtering med try/catch i UserHandler

Da er det bare å prøve seg frem. Har du andre erfaringer så ta gjerne kontakt og skriv litt om det.