Verwendetes Datenbanksystem: <SQL>
Hallo zusammen,
ich versuche irgendwie verzweifelt ein SQL Statement in LINQ abzubilden.
Zur Verfügung habe ich 3 einzelne Datenbanktabellen, dessen Keys jeweils Adressen (adrnr), Anschriften (adrnr, ansnr) und Ansprechpartner (adrnr, ansnr, aspnr) sind.
Was ich aktuell habe, bringt nicht das Ergebnis was ich versuche abzubilden.
Ich suche im konkreten von hinten nach vorne
Den Standart Ansprechpartner (der jeweiligen Anschrift), der Anschrift(en), der Adresse. 1:n:n
Eine Adresse hat IMMER mind. 1 Anschrift und kann mehrere haben, aber nicht jede Anschrift hat zwingend einen Ansprechpartner (wovon eine Anschrift aber mehrere haben kann), wenn dann muss der Ansprechpartner mit dem StdKz == true abgerufen werden
Anhand folgendes Beispiels hatte ich mich schon versucht, jedoch kriege ich damit nur Adressen zurück die einen Ansprechpartner haben.
Wenn kein Ansprechpartner vorhanden ist, kriege ich auch die jeweilige Adresse nicht.
https://www.codeproject.com/Questions/5301181/How-to-use-linq-to-join-3-tables-in-Csharp
Hat jemand einen entscheidenen Tipp für mich?
Mein aktueller Code:
var adressen = await _context.Adressen
.Include(b => b.Bild)
.Include(s => s.Status)
.ToListAsync();
var anschriften = await _context.Anschriften
.ToListAsync();
var ansprechpartner = await _context.Ansprechpartner
.Include(b => b.Bild)
.ToListAsync();
var adressenListe =
(
from adr in adressen
// Inner Join da immer eine Anschrift existiert, bezogen auf die Adressnummer
join ans in anschriften on adr.AdrNr equals ans.AdrNr
// Müsste ein LEFT Join sein, der als where ans.AdrNr == asp.AdrNr && ans.AnsNr == asp.AnsNr && asp.StdKz == true beinhaltet
join asp in ansprechpartner on ans.AnsNr equals asp.AnsNr
select new AdressenViewModel()
{
ID = ans.ID,
ERPID = ans.ID,
AdrNr = ans.AdrNr,
AnsNr = ans.AnsNr,
SuchBeg = adr.SuchBeg,
Branche = adr.Branche,
VtrNr = adr.VtrNr,
Na1 = ans.Na1,
Na2 = ans.Na2,
Na3 = ans.Na3,
Str = ans.Str,
PLZ = ans.PLZ,
Ort = ans.Ort,
LandBez = ans.LandBez,
Tel = ans.Tel,
EMail1 = ans.EMail1,
WSeite = ans.WSeite,
StdReKz = ans.StdReKz,
StdLiKz = ans.StdLiKz,
StdAnsprechpartner = asp.VNa + " " + asp.NNa,
Info = ans.Info,
Bild = adr.Bild,
GspKz = ans.GspKz,
ErstBzr = ans.ErstBzr,
AendBzr = ans.AendBzr,
ErstDat = ans.ErstDat,
LtzAend = ans.LtzAend
});
Dann schau dir mal Ausführen von Left Outer Joins an.
Oder wenn du das Original SQL-Statement hast, dann kannst du mal Linqer (SQL to LINQ converter) ausprobieren.
Danke für die Antworten.
Die Microsoft Hilfe beschreibt keine Where Clausel innerhalb des Joins.
Ebenso arbeite ich in einer Apple Umgebung und kann den Converter nicht nutzen.
Jedoch habe ich wie es so oft der Fall ist, nochmal drüber nachgedacht.
Der Fehler lag nie an meinem LINQ Tests, sondern an dem Model das ich nicht gegen NULL geprüft habe.
Der vollständige LINQ zu meiner Antwort ist:
from adr in adressen
join ans in anschriften on adr.AdrNr equals ans.AdrNr
from asp in ansprechpartner.Where(a => ans.AnsNr == a.AnsNr && a.StdKz == true).DefaultIfEmpty()
Das ausschlaggebende ist jedoch gewesen:
StdAnsprechpartner = asp == null ? "" : asp.VNa + " " + asp.NNa,
Somit wäre das Problem gelöst 😉
Du hast da extremen Spaghetti-Code, ein Horror das zu pflegen. Ist Dir bewusst, oder? 🙂
Schau Dir [Artikel] Drei-Schichten-Architektur
Verwende für Rückgabe von DB-Selects einfach Projektionen. Macht man mit allen DB-Systemen und in allen Programmiersprachen/Runtimes so.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Sind auch meine ersten Versuche in dem Umfeld, man liest eben sehr viel unterschiedliches.
Use-Cases, Pluginmethode, Drei-Schichten-Architektur.
Ich kann das noch nicht ganz so verstehen mit deinem Ansatz, ist das so in etwa der Weg?
Müsste meine Datenzugriffsschicht den anonymen select als return liefern, die Logikschicht müsste ein Viewmodel draus machen und die Präsentationsschicht ruft das fertige Model ab, zeigt an und gibt Änderungen wieder an die Logikschicht, die wiederum aus dem ViewModel ein DB Model macht, das dann in der Datenzugriffsschicht verarbeitet wird?
Ich hab ein relativ altes Video gefunden, dass erstmal logisch klingt.
Ist das so zu empfehlen?
3 Schichten Architektur
Müsste meine Datenzugriffsschicht den anonymen select als return liefern,
Es ist nicht möglich anonyme Typen zurück zu geben.
Die Datenschicht sollte eigene Modelle haben, die zurück gegeben werden. Man spricht hier von Projektionen.
IBM IT Basics: Selection and projection
Ist zwar von IBM, aber trotzdem eine korrekte Definition.
Hintergrund: man braucht nicht immer eine Entität, sondern man benötigt in 99% der Fällen ja ein gewisses Gesamt-Select einer Datenbank: die Projektion.
Neben der Entität liefert also die Datenschicht noch viele weitere Modelle, die man dann in der UI-Schicht konsumieren kann; zB in ViewModels übersetzen kann.
die Logikschicht müsste ein Viewmodel draus machen und die Präsentationsschicht ruft das fertige Model ab, zeigt an und gibt Änderungen wieder an die Logikschicht, die wiederum aus dem ViewModel ein DB Model macht, das dann in der Datenzugriffsschicht verarbeitet wird?
Siehe [Artikel] Drei-Schichten-Architektur
Nimms mir nich übel, aber auch wenn Dein verlinktes Video 14 Jahre alt is, werd ich das Video nun nicht vollständig anschauen können und reviewen, um zu sagen, obs was ist oder nicht. Da fehlt mir dann doch die Zeit für.
Die 3-Schicht-Architektur ist aber ein bewährtes Standard-Vorgehen, das es schon Jahrzehnte gibt und immer noch Gültigkeit hat. Es gibt aber natürlich auch neuere/modernere Ansätze, je nach Nutzen. Aber man sollte die 3-Schicht-Architektur schon kennen, weil sie die Basis vieler moderner Vorgehen ist.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Bin dir ja dankbar das du antwortest, dann musst du nicht noch ein Video anschauen 😉
Aber die Grundstruktur entspricht dem was du verlinkt hast.
Grundsätzlich habe ich die Struktur so auch verstanden, wo es gedanklich aber noch hängt ist bei der Übergabe der Daten in den Service.
Das Datenbank ist unterteilt in 4 Tabellen, die für den Abruf einer "Anschrift" in meinem Fall benötigt werden.
Ich würde jetzt als nächstes 3 weitere Datenmodell Klassen bilden, die meine anderen benötigten Tabellen abruft.
Im Service ruft ich also meine Daten von jedem Datenmodell ab.
Mache ich dann im Service schon das ViewModel oder Injecte ich den Service in meine blazor Komponente und mach da erst ein ViewModel draus?
Der Service sollte das ViewModel nicht kennen. Das ViewModel ist Teil der UI. Das kennt also nur die UI.
Siehe Bild in [Artikel] Drei-Schichten-Architektur
Modelle dürften von Unten nach Oben bekannt sein, aber nicht von Oben nach Unten.
UI darf die Datenmodelle kennen, aber Daten kennt keine UI Modelle. Ein Service aus der unteren Schicht kann also niemals ein ViewModel nutzen.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Alles klar verstanden, soweit dachte ich es mir schon.
Hab gerade meine Test App parallel dazu mal in das neue System gebracht.
Aktuell hänge ich noch daran, dass in der programm.cs ein Fehler kommt, den ich gerade er-google.
"Some services are not able to be constructed"
using test.Service;
builder.Services.AddScoped<IAdresseService, AdresseService>();
Kann das sein, dass man seine Beiträge nicht bearbeiten kann?
Ich werde einfach nicht fündig zu dem Problem, sorry für den vielen Code, aber ich verstehs an dem Punkt nicht weshalb das passiert.
Drei Klassenbibliotheken und ein Server Projekt
Data
Service
Model
Blazor Server = App
Referenz in Data = Service / Model
Referenz in Service = Data / Model
Referenz in App = Service
In Data ist der DBContext, ganz normal wie er sein sollte.
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
public DbSet<Adresse> Adresse { get; set; }
}
}
In Data wird mein Repository abgerufen
private readonly DataContext _context;
public AdresseRepository(DataContext context)
{
_context = context;
}
public async Task<List<Adresse>> GetAdressen()
{
var adressenTable = await _context.Adresse
.ToListAsync();
return adressenTable;
}
Im Service ist eine Klasse die das Repository in Data abruft
public class AdresseService : IAdresseService
{
private IAdresseRepository _adresseRepository;
public AdresseService(IAdresseRepository adresseRepository)
{
_adresseRepository = adresseRepository;
}
public async Task<List<Adresse>> GetAdressen()
{
List<Adresse> adressenTable = new List<Adresse>();
adressenTable = await _adresseRepository.GetAdressen();
return adressenTable;
}
}
In der Index.razor per @inject IAdresseService adresseService, rufe ich var adressenTable = await adresseService.GetAdressen(); auf
In der Program.cs ist der Context und der Service registriert
builder.Services.AddDbContext<DataContext>
(options => options.UseSqlServer("String"));
builder.Services.AddScoped<IAdresseService, AdresseService>();
Beim starten der App bekomme ich "Some services are not able to be constructed (NET.Service.IAdresseService) bezogen auf NET.Data.IAdressRepository
> Beim ganzen lesen auf google würde ich sagen, dass irgendwo die Referenz zur Klassenbibliothek fehlt.
> Hab was von DBContext Factory gelesen und so, aber irgendwie komm ich nicht weiter.
Gibt es bereits einen Artikel der mein Problem behandelt und ggf. lösen kann, oder habt ihr eine Idee dazu, wo ich was vergessen habe?
Hast du denn auch IAdresseRepository
mit der Klasse AdresseRepository
registriert?
Ja, gibt ein Artikel - sollte man sich meist vor dem Coden durchlesen 😉Dependency injection - .NET
Dependency Injection muss alle Services kennen, sonst kann es nicht funktionieren, wie TH69 schon eine potentielle Lösung dazu gesagt hat.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Vielen Dank,
das war auch die Lösung.
Ich hatte den Repository Service nicht registriert, auch nicht wirklich realisiert das die "App" den Service kennen muss, da die App keine Projektreferenz zu der Datenschicht hat.
Dachte das schlingt sich von Schicht zu Schicht, sodass nur die darüberliegende vom drunterliegenden Service wissen muss.
Die richtige Reihenfolge der Registrierung ist auch wichtig, das war mein nächster Fehler.
Die richtige Reihenfolge der Registrierung ist auch wichtig, das war mein nächster Fehler.
Dann stimmt was bei Dir nicht.
Die Reihenfolge der Registrierungen von Services - bei ASP.NET Core inkl Blazor findet das in ConfigureServices()
statt - muss immer irrelevant sein, weil diese nicht garantiert ist (zB durch Fremdabhängigkeiten).
Die Reihenfolge der Middleware (also bei ASP.NET Core in Configure()
) ist jedoch genau zu beachten.
ASP.NET Core Middleware Order
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ja da hattest du recht.
Ich bin das nochmal durchgegangen und hab das alles behoben.
Läuft nun nach dem 3-Schichten-Prinzip wie du vorgeschlagen hast.
Bringt schon Vorteile mit sich, das stimmt.