Laden...

Repository-Pattern mit ORM sinnvoll?

Erstellt von Rioma vor 9 Jahren Letzter Beitrag vor 9 Jahren 3.482 Views
R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren
Repository-Pattern mit ORM sinnvoll?

Hallo zusammen,

ich frage mich jedesmal ob das Repository-Pattern sinnvoll in Verbindung mit einem ORM ist wenn ich es implementiere.

Ich habe bisher nur mirco-ORM's wie Dapper/PetaPoco benutzt und Dapper per "plug-in" um CRUD erweitert.

Die CRUD-Operations lassen sich ja noch ganz gut im Interface und in der Klasse implementieren, aber was ist wenn die Abfragen komplexer werden?

Für jede Abfrage ne eigene Methode im Repository? Eine iqueryable Methode, oder Methoden die Bedingungen als Parameter entgegen nehmen um die Anzahl der Methoden kleiner zu halten?

Am richtigsten wäre wohl noch für jede Abfrage ne eigene Methode, aber wer möchte die Klasse dann überhaupt noch auf machen, wenn dort irgendwann weit über 100 Abfragen drin stehen?

Wie setzt ihr so etwas um? Mit oder ohne Repository? Wie würdet ihr so etwas mit einem micro-ORM umsetzen?

Danke euch 😃

16.834 Beiträge seit 2008
vor 9 Jahren

Es gibt kaum einen sinnvolleren Pattern als den Repository Pattern - und vor allem mit einem ORM.
Man setzt aber sogenannte Generic Repositories um, sodass Du nicht für jede Entität alles neu schreiben musst.

Siehe zb Grundstruktur in
Generic Unit of Work & (Extensible) Repositories Framework

T
62 Beiträge seit 2012
vor 9 Jahren

Dazu gibt es auch ein schönes Video vom Kenny Pflug: https://www.youtube.com/watch?v=G738cPs8Ack

R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren

Bei einem generischem Repository hätte ich pro Entität eine Klasse die sich um CRUD kümmert. Aber wie ist es mit "komplexeren" Abfragen?

Habt ihr dann Methoden wie:

GetPersonbyName()

GetPersonbyStreet()

usw.

Diese müssten dann ja alle mit ins interface...

F
10.010 Beiträge seit 2004
vor 9 Jahren

Am richtigsten wäre wohl noch für jede Abfrage ne eigene Methode, aber wer möchte die Klasse dann überhaupt noch auf machen, wenn dort irgendwann weit über 100 Abfragen drin stehen?

Das Repo und das UOW sind zur Abstraktion gedacht und helfen die Zugriffe auf die Datenhaltung vom Programm Fernzuhalten.

Und wenn du ein Repo mit 100 Abfragen hast, dann ist das auf jeden Fall besser als wenn du diese Abfragen über den Code verteilst.

16.834 Beiträge seit 2008
vor 9 Jahren

Diese müssten dann ja alle mit ins interface...

Korrekt.

Spezifische Methoden müssen im Repository umgsetzt werden.
Und wie FZelle schon sagt: wenn das 100 sind, dann sind das eben 100. Sind das 300 dann eben 300.

Nach Außen müssen das aber unbedingt einzelne Abfragen sein.
Wenn Du in Deinem Repository einen Mechanismus implementiertst, der die Abfragen vereinfach (GetByProperty("Street") / ("Name")). dann ist das vollkommen legitim.

P
1.090 Beiträge seit 2011
vor 9 Jahren

Auf 100 Verschiedene Abfragen wirst du wohl nur kommen, wenn du unterschiedliche belange Vermischt. Für unterschiedliche Belange unterschiedliche Repositories zu verwenden, ist schon angebracht.

Wenn du z.B. für deine Personen noch Statistiken auswerten würdest, währe es durchaus angebracht ein eigenes Repository für die Statistiken zu machen. Welches dann auch nicht unbedingt vom Generic Reposetory erben würde. Da es nur lesende Zugriffe anbietet.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren

Erstmal danke für eure Antworten.

Wenn ich nun ein generisches Repository habe und daraus z.B. ein Artikel-Repository und Artikelgruppen-Repository erzeuge, habe ich schon mal 2 Repositorys für CRUD.

Zugreifen auf diese Repository tue ich eigentlich über die Schnittstelle:


IRepository<Artikel> ArtRep = new ArtikelRepository();
IRepository<Artikelgruppe> ArtGruRep = new ArtikelgruppeRepository();

Wenn ich es so machen würde, wäre aber das Problem, dass ich alle Methode in IRepository<> haben muss, um auf die ausprogrammierten zuzugreifen. Für Standart-CRUD kein Problem, aber für große Abfragen die spezifisch sind schon.

Es würde ja keinen Sinn machen, ein generisches Repository zu nutzen, wenn ich für jede Klasse ein eigenes generisches habe.

Schiebt ihr dann zwischen dem generischen IRepository<> und dem ArtikelRepository eine weitere Schnittstelle für die spezifischen Abfragen?

Ich habe hier durch Zufall noch einen Vortrag der Basta gefunden, der eigentlich genau mein Thema abdeckt (Bis auf EF <> micro-ORM). Der Redner möchte hier eine alternative zum Repository-Pattern zeigen. Hat dies schon mal jemand von euch gesehen und könnte sein Vorgehen bewerten? https://www.youtube.com/watch?v=EmLDDb_V3LU

F
10.010 Beiträge seit 2004
vor 9 Jahren

Schiebt ihr dann zwischen dem generischen IRepository<> und dem ArtikelRepository eine weitere Schnittstelle für die spezifischen Abfragen?

Natürlich. Alles andere macht doch keinen Sinn.
Das generische Repo ist doch nur da, damit man vieles nicht mehrfach implementieren muss.

16.834 Beiträge seit 2008
vor 9 Jahren

Statt

IRepository<Artikelgruppe> ArtGruRep = new ArtikelgruppeRepository();

eben

IArtikelgruppeRepository ArtGruRep = new ArtikelgruppeRepository();

denn

public class ArtikelgruppeRepository : Repository< Artikel >, IArtikelgruppeRepository 

Das Grobkonzept des Videos finde ich okay, aber bei anderen Dingen wie ToList oder dem UoW-Container, der die Repositories kennt, würde ich den Kopf schütteln.

P
1.090 Beiträge seit 2011
vor 9 Jahren

Nicht ganz du Implementierst eine Basis Klasse von der die anderen dann Erben damit du es nicht immer neu Implementieren musst.

Das könnte grob so aussehen (ist jetzt nur so runtergeschrieben);

public class Repository<TEntity> where TEntity : class
{
    IUnitOfWork _unitOfWork;
    
    public Repository(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public IList<TEntity> GetAll()
    {
        return _session.Get<TEntity>().toList();
    }

    public void Save(TEntity entity)
    {
        _session.SaveOrUpdate(entity);
    }

    public void Delete(TEntity entity)
    {
        _session.Delete(entity);
    }
}



public class ArtikelRepository : Repository<Artikel>
{


public IList<Artikel> DeineSpezialAbfragenNurFürDenArtikel(){}

}

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

16.834 Beiträge seit 2008
vor 9 Jahren

Auch hier würde ich ToList ziemlich infrage stellen.....

R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren

wie sieht es bei euch denn mit der Business-Schicht aus?

Habt ihr eine zwischen ViewModel und DAL und tut die etwas anderes als die Daten durchzureichen?

In den aller meisten Methoden reiche ich hier nur die Daten zum VM und deswegen fühlte ich mich beim Video von der Basta angesprochen 😄

16.834 Beiträge seit 2008
vor 9 Jahren

Ich hab ein mal dem Repository Pattern im DAL, und ein mal in der BL-Schicht für die Services.
Meine Repositories liefern mit dabei nur stupide die Entitäten, und die Services erstellen mitr Business-Objekte, die ich dann in den Views über ViewModels verwende.
Ich habe aber auch einige Methoden, die nur weiter reichen, was ich nicht schlimm finde.

Aber wie gesagt; finde einige Ansätze aufgrund eigener Erfahrung in den Videos nicht gut.

R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren

Alles klar, danke euch. Hättest du noch Zeit kurz deine bedenken zu nennen? Stichworte würden natürlich reichen, einige Sachen werden mir dann wahrscheinlich selber auf fallen.

16.834 Beiträge seit 2008
vor 9 Jahren

ToList erzeugt den Query und feuert ihn.
Das heisst, solange Du mit IEnumerable und IQueryable werden keine Daten geladen.
GetAll mit ToList lädt aber die komplette Datenbanktabelle mit allen Feldern. Wie oft braucht man das denn wirklich?
Ich sehe hier in 99,9% aller Anwendungen die Gefahr, dass unnötig viele Informationen aus der Datenbank unbewusst geladen werden.
==> verdammt sinnlos
Danach motzt man lieber über das Entity-Framework wie langsam der Schei* ist anstatt zu verstehen, was überhaupt passiert.

Gleichzeitig ist ToList aber in einigen Konstellationen sehr sinnvoll.
Erfolgt ein foreach auf ein IQueryable, dann wird für jeden Schleifedurchlauf ein Single-Query für das eine item ausgelöst. Ist die IQueryable also 20 Einträge groß erfolgen 20 Selects.
Würde man hier ein ToList ausführen, dann würden alle 20 mit einem Select gelden werden
=> sehr sinnvoll.

Man muss also mit ToList bewusst umgehen, damit das ganze Zeug nachher effizient ist.
Für mich gehört eine Materialisierung nicht in den DAL, sondern in den Business-Layer. Der weiß, ob man wirklich alle Informationen braucht, oder nicht.
Bringt natürlich nur was, wenn man auch einen Business-Layer hat, wa?
Der Herr in EF und Repository, Datenbankänderungen nachladen, der den DAL direkt an die UI bindet und kein BL hat; da ist das natürlich dann.. unpraktisch 😉

Dann noch der UoW Container.
In meinen Augen stellt der UoW-Container nur eine Verbindung dar. In den beiden Beispielen gibt es aber eine strenge Beziehung zwischen UoW-Container und Repository.
Denn die Repositories werden im UoW gelistet. Und das finde ich nicht nur schlecht, sondern falsch.

R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren

Danke für die ausführliche Antwort!

R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren

Hallo nochmal,

ich habe mir in der Zwischenzeit weitere Implementierungen angeguckt und dabei ist mir eine Kleinigkeit aufgefallen, die ich gerne mal auf mein Beispiel beziehen würde:

In der IRepositoy Schnittstelle befindet sich meistens eine GetAll Methode.
Wäre es vollkommen falsch bzw. seht ihr für die Zukunft Probleme, wenn diese ein Predicate oder eine Where clause entgegen nimmt?

Hierüber würden dann einfache Abfragen nach bestimmten Eigenschaften gemacht werden. Bei komplexeren Abfragen die Joins oder ähnliches verlangen, gibt es dann eigene Methoden.

Mit Dapper + SimpleCrud so in der Art:



IList<Artikelgruppe> GetAll (string where){

 return connection.GetList<Artikelgruppe>(where);  
}

Ich weiß nicht was GetList macht wenn man null übergibt, aber das wäre ja schnell überprüft und ohne Bedingung bekommt man halt die komplette Tabelle.

2.207 Beiträge seit 2011
vor 9 Jahren

Hallo Rioma,

gib doch für den Filter eine Expression<Func>... an und setze die Standardmässig auf null.

Beispiel

public virtual IEnumerable<TEntity> Get(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            IQueryable<TEntity> query = dbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query).ToList();
            }
            else
            {
                return query.ToList();
            }
        }

aus

Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10)

Über das ToList lässt sich streiten (Sie Abts Antwort oben), aber so kannst du Queries filtern, wenn du das willst.

Gruss

Coffeebean

R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren

Danke, ging nur um die Theorie, ob ihr da Probleme seht. So könnte man einfache Abfragen mit einer Methode abdecken und hätte für jede Eigenschaft nicht eine eigene wie

GetArtikelByName
GetArtikelByEAN

usw...

Ich habe in einigen Implementierungen diesen Ansatz gesehen, aber wiederum auch Implementierungen, die für jede Abfrage eine eigene Methode hatten und ich war mir deswegen unsicher, ob es hierfür spezielle Gründe gibt.