Laden...

Repository in EF 4.1 korrekt einsetzen

Erstellt von mygil vor 12 Jahren Letzter Beitrag vor 11 Jahren 3.899 Views
M
mygil Themenstarter:in
124 Beiträge seit 2009
vor 12 Jahren
Repository in EF 4.1 korrekt einsetzen

verwendetes Datenbanksystem: SqlServer2008 Developer Edition

Hallo!

Kann mir bitte jemand zur Allgemein korrekten Verwendung von Repository's weiterhelfen?

So schaut derzeit mein Bestell-Repository aus:


    public class WMS_BestellungRepository : IWMS_BestellungRepository
    {
        public IQueryable<WMS_Bestellung> GetBestellungen()
        {
            var dataContext = new UMSEntities();
            var bestellungen = from b in dataContext.WMS_Bestellung
                               select b;
            return bestellungen;
        }
        public IQueryable<WMS_BestellungViewModel> GetBestellungenProjected(int? iBestellungID)
        {
            var projectedBestellung = from b in GetBestellungen()
                                        select new WMS_BestellungViewModel
                                        {
                                            Auftragswert = (from d in b.WMS_BestellungDetail
                                                            select d.Einzelpreis * d.Menge).Sum(),
                                            Bemerkung = b.Bemerkung,
                                            BestellDatum = b.BestellDatum,
                                            BestellungID = b.BestellungID,
                                            BestellungNr = b.BestellNr,
                                            FussText = b.FussText,
                                            GesamtMenge = (from d in b.WMS_BestellungDetail
                                                          select d.Menge).Sum(),
                                            KopfText = b.KopfText,
 
                                            LieferantAdresse = b.WMS_Kontakt.Adresse,
                                            LieferantFaxNr = b.WMS_Kontakt.FaxNr,
                                            LieferantKontaktperson = b.WMS_Kontakt.Kontaktperson,
                                            LieferantKreditorNr = b.WMS_Kontakt.KreditorNr,
                                            ...
                                        };
            // Filter nach BestellungID
            if (iBestellungID.HasValue)
                projectedBestellung = projectedBestellung.Where(b => b.BestellungID == iBestellungID);

            return projectedBestellung;
        }

Mittels dem folgenden Code erstelle ich ja einen neuen Context:


var dataContext = new UMSEntities();

Wenn ich jetzt aber weitere Repositories schreiben möchte, sollte ich dann jedesmal einen neuen context erstellen oder den vorhanden verwenden?
Falls den vorhandenen verwenden - wie?

lg myGil

16.807 Beiträge seit 2008
vor 12 Jahren

Spezifische Repositories haben in der Regel eine Basis, von der man erbt.
Diese Basis hat einen Konstruktor, dem man den Kontext übergibt. Zudem erhält sie bereits allgemeine Methoden, wie Add, Delete, Get, GetMany, GetAll, damit diese nicht in allen anderen Repositories einzeln definiert werden müssen.


    public class BenutzerRepository : RepositoryBase<Benutzer>, IBenutzerRepository
    {
        public BenutzerRepository( MyContext dbContext )
            : base( dbContext )
        {
        }

Die Verwendung wäre dann


            using (var dbContext = DatabaseConnectionFactory.Single)
            {
                using (var benutzerRepository = new BenutzerRepository(dbContext))
                {
                    var benutzer = benutzerRepository.GetBenutzerByKennung(benutzerKennung);

Wobei anzumerken wäre, dass meine Dispose-Implementierung der Repository gewollt niemals den Kontext schließt, sondern vor allem dem Scoping dient.

Das Repository selbst eröffnet niemals einen Kontext, sondern erhält diesen immer, damit man diesen eben mit anderen Repositories teilen kann.
Aber es gibt im Web unheimlich viele verschiedene Repository Beispiele - da hättest Du Dir das auch abschauen können.

Ein weiterer Fehler an Deinem Repository:
Es gibt IMMER die Entity zurück und niemals irgendein ViewModel.

Hier empfiehlt sich für alles eigene Projekte anlegen, damit Dir selbst die Trennung klarer wird:
* Projekt der Anwendung
* eigenes Projekt der ViewModels
* eigenes Projekt der Entities
* eigenes Projekt der Repository

Zudem kannst Du Properties wie den Auftragswert des ViewModels als reine GET-Implementierung darstellen, sodass man der Property direkt ansieht, dass sie sich aus bereits bestehenden Einzelwerten ermittelt.

Aber das sind Grundlagen und stellt hier nur ein Hinweis dar.

I
302 Beiträge seit 2008
vor 12 Jahren

Noch eine Frage zu deinem Repository: Warum gibst du IQueryable zurück und keine Liste? Die Abfrage wird so noch nicht ausgeführt. Einige Operationen sind mit IQueryable auch nicht möglich.

1.552 Beiträge seit 2010
vor 12 Jahren

Hallo itstata,

Warum gibst du IQueryable zurück und keine Liste?

M.E. ist dies auch die richtige vorhergehensweise, denn dann kann man diese gegebenfalls noch filtern. Ich gebe dir Recht, dass eine IQueriable bei ViewModels keinen Sinn macht, sondern nur bei Datenobjekten.
An sich wird dann die Query spätestens beim ersten foreach,ToList, Count...ect ausgeführt.

Gruß
Michael

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

16.807 Beiträge seit 2008
vor 12 Jahren

Warum gibst du IQueryable zurück und keine Liste? Die Abfrage wird so noch nicht ausgeführt. Einige Operationen sind mit IQueryable auch nicht möglich.

Dem stimme ich zu, wenn Du das auf die ViewModels beziehst.
Aber Du solltest niemals gerenell eine Liste zurück geben und damit das Materialisieren erzwingen; absolut kontraproduktiv für die Performance!!

DbSet bzw IQueryable sind hier generell schon korrekt. Wer Methoden nutzen will, die in IQueryable nicht enthalten sind, dann caste eben entsprechend - aber nicht in der Schnittstelle.

I
302 Beiträge seit 2008
vor 12 Jahren

Was ich z.B. meine ist, dass du bestimmte String-Operationen nicht ausführen kannst, da IQueryable kein SQL-Pendent kennt. Das führt dann direkt zum Laufzeitfehler.

Wenn ich Performance benötige, erstelle ich lieber eine zweite Methode.

1.552 Beiträge seit 2010
vor 12 Jahren

An sich dürfte die oben genannte Weise auch nicht funktionieren, denn man kann nicht ein .Select(m=>new ViewModel[...]) machen ohne vorher eine Materialisierung durchgeführt zu haben, da GetBestellungen() in diese Weise nicht materialisiert.

Was ich z.B. meine ist, dass du bestimmte String-Operationen nicht ausführen kannst

Z.b.? Contains, StartsWith... funktionieren.

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

16.807 Beiträge seit 2008
vor 12 Jahren

An sich dürfte die oben genannte Weise auch nicht funktionieren,

Doch doch, das würde klappen.
Es würde aber bei jedem Schleifenelement ein spezifischer SQL-Query gesendet.

Was ich z.B. meine ist, dass du bestimmte String-Operationen nicht ausführen kannst

Mir fällt da spontan nichts ein...?
Einzig wenn es um Property-Erweiterungen geht, die von definierten Properties Daten nutzen, führen zu einem Laufzeitfehler. Aber dafür wiederum gibts Expression<Func<....>>

Wenn ich Performance benötige, erstelle ich lieber eine zweite Methode.

Sorry aber das ist doch Käse. Das macht doch die Schnittstelle völlig unübersichtlich und ist wirklich das schlechteste was man in Sachen Performance machen kann.
Wann braucht man denn jemals alle Einträge geschweige denn alle Properties des entsprechenden Typs.. 🤔

Ist so als ob man dauernd im ersten Gang Auto fahren würde, frei nach dem Motto "irgendwann werd ich schon mal an ner Ampel stehen bleiben und wieder anfahren müssen".

I
302 Beiträge seit 2008
vor 12 Jahren

Nehmen wir an wir haben folgendes Objekt:


class Auftrag{

public int Nummer {get;set;}
public string Bezeichnung{get;set;}
}

Jetzt möchtest du für eine Automcomplete-Box eine Suche in der Datenbank starten. Der Nutzer kann also Nummer und Bezeichnung eingeben. Bei der Suche benötigst du dann den Ausdruck: angebot=>angebot.Nummer+angebot.Bezeichnung == suchwort.

String-Operation ist dann natürlich falsch ausgedrück. Ich hoffe aber du verstehst, was ich meine. Generell versuche ich auf jeden Fall den Wildwuchs mit Rückgabewerten gering zu behalten. Ich würde da lieber noch eine separate Methode hinzufügen. Ich denke schon, dass der Großteil der Repository-Anfragen im Bereich GetXy, GetAllXy liegt. Wenn hier sinnvoll sortierte Listen zurückkommen sollte das für die meisten Fälle passen. So sieht das zumindest bei mir im Umfeld aus.

edit: Meinst du nicht, dass IQueryable dazu verleitet irgendwo im Frontend noch Business-Logik zu implementieren?

1.552 Beiträge seit 2010
vor 12 Jahren

@istata, doch dies geht ab .NET 4.0


return a => SqlFunctions.StingConvert((double)a.Nummer) + a.Bezeichnung) == suchwort

SqlFunctions.StringConvert

Meinst du nicht, dass IQueryable dazu verleitet irgendwo im Frontend noch Business-Logik zu implementieren?

Nein, verleiten nicht, aber eventuell ermöglichen.

Gruß
Michael

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

16.807 Beiträge seit 2008
vor 12 Jahren

Bei der Suche benötigst du dann den Ausdruck: angebot=>angebot.Nummer+angebot.Bezeichnung == suchwort.

Genau dafür gibts Expressions. Oder eben die Variante von xxMUROxx.
Das hat nichts mit IQueryable zutun. 🙄

Was Du mit Deinem ToList() erreichst (falls ich es Dir erklären muss):
IQueryable würde es - so wie gedacht und besser ist - auf dem SQL Server ausführen.
Was Du machst ist im Prinzip ein SELECT * FROM TABLE und dann auf der Anwendungsseite alles durchläufst und das sammelst, was passt...

Ich denke schon, dass der Großteil der Repository-Anfragen im Bereich GetXy, GetAllXy liegt.

Das bezweifel ich massiv! Da unterschreib ich aber das Gegenteil 😉
Ich kann an meiner Webanwendung auslesen, welche Seite wie oft aufgerufen wurde.. die gefilterte Liste der Elemente (standardmäßig 50 Einträge) hatte gestern einen Anteil von fast 34%. Die Detailseite von Elementen liegt bei bisschen mehr als 11%.

Ein Get, das eine Entity zurück gibt, braucht sowieso kein ToList. Und dass hiervon im Regelfall alle Properties genutzt werden; nie!

GetAll entspricht quasi SELECT * FROM TABLE. Von meiner aktuellen Anwendung hat die Datenbank insgesamt um die 2,5 GB aufgeteilt auf 42 Tabellen / Entities. Was meinste was hier ein GetAll() für die Performance anrichten würde. Geschweige denn, dass der User überhaupt mit all den Informationen arbeiten könnte.....

1.552 Beiträge seit 2010
vor 12 Jahren

Aufgrund von Abt gesagtem, macht es deutlich warum ein IQueriable<Entity> essentiell ist. Denn dadurch ist man imstande (angenommen Entity ist eine Tabelle mit 50 Spalten) mit Select nur 2 Spalten davon zu holen, zu filtern evtl demensprechend gruppieren, bzw. noch zu sortieren. Dies ist mit einer ToList zwar auch möglich, jedoch habe ich dabei 48 Spalten zusätzlich den ausgefilterten Daten sinnlos geladen. Generell soll man den SQL-Server so viel wie möglich rechnen lassen.

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

I
302 Beiträge seit 2008
vor 12 Jahren

@istata, doch dies geht ab .NET 4.0

  
return a => SqlFunctions.StingConvert((double)a.Nummer) + a.Bezeichnung) == suchwort  
  

Wow nicht schlecht, das wußte ich noch nicht 😃

@Abt
Ich denke nicht, dass du mir erklären musst wie sich ToList() etc. auf das generierte SQL auswirkt.

Aber ich versuche meine Bedenken gegenüber dem gepostete Repository noch etwas anders zu erläutern. Was ich auf keinen Fall möchte ist, dass Business-Logik im Sinn von DRY mehrfach im Code steht. Ich habe demnzufolge meine Repositories die mir natürlich auch nur die Datensätze bringen sollen, die benötigt werden. Darüber brauchen wir gar nicht diskutieren.
Was ist deiner Meinung nach die Aufgabe eines Repositories? Ich denke es sollte für den Zugriff auf die Daten konzipiert sein. Mit IQueryable verlagerst du diese Logik in eine tiefere Schicht. Damit kann ich mich irgendwie wirklich schwer anfreunden. Zumal die Methode "GetXy" heißt - aber gar nichts "Gettet".

Das Argument mit dem zurückgeben sämtlicher Spalten finde ich wirklich stark. Lasst mich mal eine Nacht darüber schlafen 😃
Ich könnte mich auch damit abfinden IQueryable zu nutzen, aber im Frontend sollte nach meiner derzeitigen Meinung davon nichts mehr zu sehen sein.

16.807 Beiträge seit 2008
vor 12 Jahren

Nein, ein Repository stellt eine Schnittstelle zur Datenbank dar. Es sorgt dafür, dass man in der Anwendung einheitliche Methoden zur Verfügung hat, ganz egal, welche Datenbank drunter steckt - es gibt die spezifischen Entities zurück.
Zudem fördert es die Modularisierung, zB wie in meinem Beispiel einen spezifischen User zu suchen.
Man könnte das auch mit Get(b=>b.Name.Equals(name, StringComparison.OrdinalIgnoreCase) lösen, aber dann muss ich überall den Code anfassen, wenn ich zB die StringComparison anpassen möchte.

Ein Repository sollte generell auch nichts sortieren; es filtert höchstens. (ausgenommen spezifische Methoden, die auch dementsprechend benannt sind).

Die Business-Logik kommt zwischen die Anwendung und das Repository.

I
302 Beiträge seit 2008
vor 12 Jahren

Da will ich nicht widersprechen.

Welche Rückgabewerte hast du denn in deiner Businesslogik? Ich vermute die Ergebnisse sind dann fix und es wird sich nur noch um Anzeige etc. gekümmert. Wenn wir schon bei dem Thema sind würde ich gerne mal wissen, wie du deine Businesslogik organisierst. Ist das ein ViewModel oder hast du eine separate Klassen - "Schicht"?

16.807 Beiträge seit 2008
vor 12 Jahren

Ich arbeite ausschließlich mit ASP MVC; aufgrund einiger Besonderheiten von MVC sind in gewissen Dingen ViewModels, die eine Entity wiederspiegeln, hinderlich (Proxybinding etc).

* Generell arbeite ich aber mit ViewModels wo ich kann, ja.
* Generell verwende ich aber keine Materialisierung.

M
mygil Themenstarter:in
124 Beiträge seit 2009
vor 12 Jahren

Erstmal Vielen Dank für die vielen Antworten!
Jetzt habe ich erstmal einiges zu kauen bis ich meine eigenen weiteren Fragen verstehe 😃
lg myGil

Z
322 Beiträge seit 2006
vor 11 Jahren

@Abt

Was ist denn bei dir

DatabaseConnectionFactory.Single

Danke