Jeg har længe søgt efter en relativ generisk måde hvorpå jeg kan
strukturere mit repositoryinterface og jeg er efterhånden kommet
frem til et repository interface, som dækker de fleste af mine
behov for CRUD og listeudlæsninger.
void Load(FO, id);
void Save(FO);
void Delete(id);
IEnumerable<FO> GetList(count, page, out total, criteria);
Load modtager et instantieret forretningsobjekt (her betegnet
FO, men skal have den konkrete type eller et interface) og fylder
objektet med data fra repository med det angivne id. Hvis data ikke
findes rejses en kørselsfejl, for at undgå at objektet skal
implementere en sporing af, om data er indlæst eller ej.
Alternativt kan Load returnere en boolsk falsk.
Save tager data i FO og oprette eller opdatere data i
repository. Id i FO opdateres til at modsvare data i repository,
hvis der er tale om en oprettelse i repository.
Delete sletter data for FO i repository og nulstiller FO's id,
således dette kan gemmes igen og dermed foranledige en oprettelse,
hvis dette skulle findes nødvendigt.
GetList modtager nogle parametre som hjælper med paginering af
udtrækket. Count er det antal elementer der skal udtrækkes og page
er den side i det total udtræk (jf. de øvrige parametre i
criteria-objektet), hvorfra elementerne skal udtrækkes. Total er
det antal elementer der total set er i udtrækket. Criteria
indeholder et (variabelt) antal parametre, som er relevante for det
aktuelle FO's kontekst. Dette kunne f.eks. være søgetekst eller
hvorvidt de udtrukne FO skal have en bestemt status. Det er helt op
til kravene til FO og systemet.
I praksis
Et eksempel på hvordan dette kan tage sig ud i praksis kunne se
ud som vist i det følgende.
Præmisserne her er, at data gemmes i en SQL Server og ORM er
LinqToSql. Der tages udgangspunkt i et personregister, hvor hver
person har registreret nogle oplysninger omkring sig, der giver
lidt muligheder for at udforske metoden omkring søgning og
paginering. Forretningsobjektet spejler i dette eksempel tabellen
person databasen, så definitionen af databasen er undladt her.
Først defineres FO:
public class Person
{
public int Id { get; set; }
public string Navn { get; set; }
public string Adresse { get; set; }
public string Postnr { get; set; }
public string Bynavn { get; set; }
public string Email { get; set; }
public string Telefonnr { get; set; }
}
Kriterieobjektet kunne se således ud:
public class PersonSearchCriteria
{
public string searchText { get; set; }
}
Dernæst defineres repositoryobjektet:
public class PersonRepository
{
public void Load(Person person, int id)
{
using(var db = new personDataContext())
{
var dbPerson = db.person.SingleOrDefault(p => p.Id == id);
if(dbPerson != null)
throw new Exception("Person ikke fundet");
person.Id = dbPerson.Id;
person.Navn = dbPerson.Navn;
person.Adresse = dbPerson.Adresse;
person.Postnr = dbPerson.Postnr;
person.Bynavn = dbPerson.Bynavn;
person.Email = dbPerson.Email;
person.Telefonnr = dbPerson.Telefonnr;
}
}
public void Save(Person person)
{
using(var db = new personDataContext())
{
var dbPerson =
db.person.SingleeOrDefault(p => p.Id == person.Id);
if(dbPerson == null)
{
dbPerson = new person();
db.person.InsertOnSubmit(dbPerson);
}
dbPerson.Navn = person.Navn;
dbPerson.Adresse = person.Adresse;
dbPerson.Postnr = person.Postnr;
dbPerson.Bynavn = person.Bynavn;
dbPerson.Email = person.Email;
dbPerson.Telefonnr = person.Telefonnr;
db.SubmitChanges();
person.Id = dbPerson.Id;
}
}
public void Delete(int it)
{
using(var db = new personDataContext())
{
var dbPerson = db.person.SingleOrDefault(p => p.Id == id);
if(dbPerson != null)
{
db.person.DeleteOnSubmit(dbPerson);
db.SubmitChanges();
}
}
}
public IEnumerable<Person> GetList(
int count,
int page,
out int total,
PersonSearchCriteria criteria
)
{
using(var db = new personDataContext())
{
var list = db.person.AsQueryable();
if(!String.IsNullOrWhiteSpace(criteria.searchText))
{
var txt = criteria.searchText;
list = db.person.Where(p =>
p.Navn.Contains(txt)
|| p.Adresse.Contains(txt)
|| p.Postnr.Contains(txt)
|| p.Bynavn.contains(txt)
|| p.Email.Contains(txt)
|| p.Telefonnr.Contains(txt));
}
total = list.Count();
if(count > 0)
{
if(page > 1)
list = list.Skip((page - 1) * count);
list = list.Take(count);
}
return
from p in list
select new Person {
Id = dbPerson.Id,
Navn = dbPerson.Navn,
Adresse = dbPerson.Adresse,
Postnr = dbPerson.Postnr,
Bynavn = dbPerson.Bynavn,
Email = dbPerson.Email,
Telefonnr = dbPerson.Telefonnr
};
}
}
}
Med dette kan man udtrække en liste af alle personer i databasen
eller en liste af personer som møder visse kriterier, her et ord
som indgår i navn, adresse, postnr, by, email eller telefonnr.
Kriterierne kan udvides efter behov og selve kaldet til databasen
bliver aldrig mere komplekst end der er behov for, fordi
kriterierne undlades, hvis der ikke er specificeret nogen
søgetekst. På samme måde undlades paginering, hvis count, dvs. det
ønskede antal personer, er mindre end 1 (reglen er implicit eller
pr. konvention om man vil).
Brugen af repository kunne se således ud og ligge som en static
metode på Personobjektet eller i en factoryklasse til Person.
Factoryklassen kunne se såleds ud:
public class PersonFactory
{
private readonly PersonRepository _repository;
public PersonFactory(PersonRepository repository)
{
_repository = repository;
}
public IEnumerable<Person> GetAll(
int count,
int page,
out int total
)
{
return _repository.GetList(count, page, total, null);
}
public IEnumerable<Person> Search(
int count,
int page,
out int total,
string searchText
)
{
var criteria = new PersonSearchCriteria {
searchText = searchText
};
return _repository.GetList(count, page, total, criteria);
}
}
Som det kan ses, implementerer factoryklassen et mere
specialiseret interface der ligger tættere på forretningskravene og
som er mere ligetil at benytte. Dette sker uden at
repository-interfacet bliver forplumret af flere metoder.
Hvis man ønsker at implementere flere kriterier med tiden, skal
disse blot lægges i PersonSearchCriteria-objektet og derefter skal
GetList-metoden ændres så de nye kriterier håndteres. Dermed ændres
der ikke på repositoryinterfacet og man sparer derfor, at skulle
rette i evt. andre klienter som er afhængige af
respositoryklassen.
Se også
En artikel om et generisk
repository, som kan gøre hele arbejdet med repositories noget
enklere.