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
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.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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.
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
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.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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.
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.
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".
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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?
@istata, doch dies geht ab .NET 4.0
return a => SqlFunctions.StingConvert((double)a.Nummer) + a.Bezeichnung) == suchwort
Meinst du nicht, dass IQueryable dazu verleitet irgendwo im Frontend noch Business-Logik zu implementieren?
Nein, verleiten nicht, aber eventuell ermöglichen.
Gruß
Michael
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.....
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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.
@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.
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.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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"?
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.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Erstmal Vielen Dank für die vielen Antworten!
Jetzt habe ich erstmal einiges zu kauen bis ich meine eigenen weiteren Fragen verstehe 😃
lg myGil
@Abt
Was ist denn bei dir
DatabaseConnectionFactory.Single
Danke