Laden...

Warum werden Entitäten und deren Beziehungen in ASP.NET Core Views nicht angezeigt?

Erstellt von JimStark vor 4 Jahren Letzter Beitrag vor 3 Jahren 4.778 Views
Thema geschlossen
JimStark Themenstarter:in
309 Beiträge seit 2020
vor 4 Jahren
Warum werden Entitäten und deren Beziehungen in ASP.NET Core Views nicht angezeigt?

Hi, folgender Code:


    public class Order
    {
        public int Id { get; set; }
        public string Title { get; set; }

        public Customer Customer { get; set; }
    }

    public class Customer
    {
        public int Id { get; set; }
        public string Name_Last { get; set; }
        public string Name_First { get; set; }
        public List<Order> Orders { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return Name_Last + ", " + Name_First;
            }
        }
    }

Die Objekte haben ich in einem DbContext. Am Start lege ich mir mit einer Seed-Klasse ein paar Beispiel Daten an und verknüpfe dort die Objekte bereits mit order1.Customer = customer1 usw.
Das geht soweit und mir wird in der Datenübersicht der Tabelle das auch angezeigt (also der Fremdkey).

Wenn ich jetzt aber in den Views (Order-Details) den Customer anzeigen lassen will, erscheint gar nichts.


        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Customer.FullName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Customer.FullName)
        </dd>

Der DisplayName wird angezeigt aber kein Inhalt.
Kann mir jemand helfen? Danke!

P
441 Beiträge seit 2014
vor 4 Jahren

Vermutlich hat er einfach die Child Items nicht geladen, dies könntest du in deinem Query mittels

Include(c => c.Orders)

lösen.

Allerdings solltest du eine saubere Schichtentrennung verwenden, dann würde das Problem nicht auftreten:
[Artikel] Drei-Schichten-Architektur

16.806 Beiträge seit 2008
vor 4 Jahren

Der empfohlene Weg sind Projektionen: die Business-Schicht ist dabei in der Verantwortung.
Tools wie AutoMapper unterstützen dabei die Query-Erzeugung mit Hilfe von Expressions.
AutoMapper - ProjectTo

Wie aber Papst schon sagte musst Du ordentlich die Schichten trennen.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 4 Jahren

Danke für die Antworten!

Der empfohlene Weg sind Projektionen: die Business-Schicht ist dabei in der Verantwortung.
Tools wie AutoMapper unterstützen dabei die Query-Erzeugung mit Hilfe von Expressions.

>

Wie aber Papst schon sagte musst Du ordentlich die Schichten trennen.

Du meinst jetzt dass ich im Controller, beim Aufruf von Order Details z.B. ein ViewBag übergebe mit dem Customer der Order oder?



public List<Order> Orders { get; set; }


Also das dann weglassen und nur Customer.Where(p=>p.Id==CustomerId).First() im Controller übergeben?

P
441 Beiträge seit 2014
vor 4 Jahren

In dem von mir verlinkten Artikel ist es gut beschrieben. Wenn du eine Schichtentrennung zwischen Datenhaltung und Datenpräsentation einführst und eigene Klassen in den Schichten hast, musst du zwischen diesen Klassen eine Projektion erzeugen.

Für die Projektion kannst du AutoMapper verwenden - musst es aber nicht.

Das Grundproblem (aber nicht die Lösung) ist, dass Entity Framework per default bei 1:n Verknüpfungen automatisch alle Datensätze mitlädt. Vermutlich ist, wenn dein Model gerendert wird, bereits die Datenbank Verbindung wieder geschlossen und damit können die Datensätze nicht automatisiert nachgeladen werden.
AutoMapper würde bei einer ausgeführten Projektion die Datensätze aber zugreifen wollen und diese damit nachladen (gesetzt dem Fall, du hast EFCore so konfiguriert, dass es das tut - ich bin aber nicht sicher, ob es das in der aktuellen Version überhaupt kann, ich habe das zum letzten mal in EF 6 benutzt).

Edit: verlinkt ist nicht gepostet - im Text korrigiert

16.806 Beiträge seit 2008
vor 4 Jahren

Du meinst jetzt dass ich im Controller, beim Aufruf von Order Details z.B. ein ViewBag übergebe mit dem Customer der Order oder?

Nein, das ist völlig falsch.

Deine Business Logik gehört gar nicht in die ASP.NET Anwendung sondern in eine Bibliothek.
Die Logik rufst Du dann im Controller auf.

Beispiel: wir haben hier eine Profilansicht im Forum, dann würde die Action so aussehen:

        [Route("{userId:int}/{userName}", Name = PortalRouter.Routes.UserProfileView)]
        public async Task<IActionResult> Profile(int userId, string userName)
        {
            // business logik aufrufen um die projektion des users zu laden
            var p = await _eventDispatcher.Send(new GetUserProfileViewProjection(userId));
            
            // projektion validieren
            if (p is null) return View("~/Views/Errors/Error.cshtml",
                     new ErrorViewModel(404, $"Es wurde leider kein Profil mit der Id #{userId} gefunden.", null))
                     .WithStatusCode(HttpStatusCode.NotFound);

            // viewmodel erzeugen
            UserProfileViewModel vm = new UserProfileViewModel(p.Profile, p.Avatar, p.ForumStats, p.LatestActivity, p.LatestPost);
            // an die view übergeben
            return View(ViewsRoot + "UserProfile.cshtml", vm);
        }

(Ist ein echtes Beispiel)

Bitte keinen ViewBag für sowas verwenden; dafür ist der ViewBag nicht da.
ViewModels werden sauber als Parameter via View() übergeben.
Dokumentation dazu: ASP.NET Core - How controllers specify views

Wie man Logik auslagert hat ja Papst verlinkt.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 4 Jahren

Meinst du jetzt ich solle den Datenzugriff komplett vom ASP.NET Projekt auslagern? Also es z.B. in eine DLL in der Projektmappe aussourcen?
Und von der dann aus mit nem ORM Mapper auf die DB zugreifen?

Oder meinst du die Modell, Controller, View innerhalb von ASP?

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 4 Jahren

Super okay! Der Post drüber bezog sich auf die Antwort von Papst

Mit was arbeite ich dann in der Bibliothek am besten? Entity Framework?

Hinweis von Abt vor 4 Jahren

Bitte keine Full Quotes

16.806 Beiträge seit 2008
vor 4 Jahren

Es gibt nicht den pauschal einen Weg, wie man Quellcode outsourced.

Die Grundidee ist, dass Dein Code von jeder Art von Anwendung funktioniert und nutzbar ist; egal ob ASP.NET, WPF oder eine Konsolenanwendung.
Daher kann die Logik nicht Teil des Anwendungsprojekts sein.

Im .NET Umfeld gibts dafür die Klassenbibliotheken, die dann referenziert werden um sie dann in Anwendungen wie ASP.NET oder WPF zu nutzen.

Wenn Du Schichten ordentlich trennst und die Grundkonzepte von Laden von Modellen umsetzt, dann ergibst sich auch Dein Problem nicht mehr.
Denn letzten Endes entsteht das Lade-Problem, was Du hast, durch den Konzeptfehler.

Im Endeffekt ist mein Codesnippet was Du da siehst quasi die "maximale Ausbaustufe".
Halte Dich einfach an die Grundidee von [Artikel] Drei-Schichten-Architektur

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 4 Jahren

Nochmal kurz was zum Verständnis dazu:

Selbst wenn ich das in Schichten trenne löst das ja nicht mein Problem.
Ich greife ja dann nur auf den DbContext über eine DLL zu.

Also ist die eigentliche Lösung für das Problem:

Include(c => c.Orders)

Oder?

16.806 Beiträge seit 2008
vor 4 Jahren

Schau Dir an, wie EF - und hier in diesem Falle Lazy Loading - funktioniert.

Include forciert Eager Loading, weil Du in Deiner aktuellen Implementierung kein Lazy Loading verwenden kannst. bzw. funktioniert es in dieser Form eben nicht.
Daher hat Dich Papst auch auf Include() hingewiesen, was Eager Loading bezweckt.

Der allgemein korrekte Weg wäre hier ohnehin das Vorgehen im Sinne von Eager Loading (Projektionen sind quasi auch Eager Loading), da ansonsten zig Calls gegen die Datenbank gehen (statt einem) und das entsprechend unperformant werden kann.

Aber wie gesagt: schau Dir die Funktionweise von EF an; nur dann hast Du auch ein Verständnis dazu.
Wenn man EF "irgendwie" entwickelt, dann endet das meist mit nem harten Knall gegen die Wand.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 4 Jahren

Ok danke, nochmal zu den Schichten:

Test.Objects.dll - Objekte/Models mit denen gearbeitet wird
Test.Data.dll - Stellt den DbContext zur Verfügung
Test.Controller.dll - Stellt die Views,... zur Verfügung
Test.Host.dll - das eigentliche ASP.NET Projekt holt sich nur noch Views, etc. aus unteren Schichten

Wäre es so "nach Stand der Technik" richtig ein größeres Projekt zu strukturieren? Gibt es dazu irgendwo gute praktische Beispiele?

16.806 Beiträge seit 2008
vor 4 Jahren

Die Auftrennung von Code in mehrere Projekte hat prinzipiell nichts mit der Problematik zutun.
Namespaces sind in C# ein Mechanismus um Code zu organisieren. Die Organisation richtet sich dabei nach der Verwendung; nicht nach der Aufspaltung in "Schichten".

Die Aufteilung in solche Dinge wie "Data" "Controller" "Objects" etc.. sind meistens ein Hinweis, dass man den Sinn von Namespaces noch nicht verinnerlicht hat, wozu diese eigentlich da sind.

Man könnte Deine gesame Problematik in einem eigenen Namespace organisieren und trotzdem alles sauber lösen.
Der Hinweis auf die Trennung in eine Klassenbibliothek diente Dir dazu, dass Du verstehst, was der Sinn in Code Organisation ist: dass Du den Code potentiell wiederverwenden kannst.

Eine weit verbreitete, simple Grundarchitektur bei ASP.NET ist:

  • Repositories für das Laden von Daten (EF Core ist dabei bereits die Darstellung vom Repository Pattern)
  • Services für die Business Logik
  • Verwendung der Services in den Actions

In vielen Samples (es sind nur Beispiele!) sieht man die Verwendung der Repositories direkt in den Actions.
Damit spart man sich vor allem am Anfang natürlich etwas Code; oft hat man aber später dann redundanten Code in den Actions.

namespace Firmenname.Produkt.Models
{
    public class CustomerBusinessModel
    {

    }
}

namespace Firmenname.Produkt.Services
{
    public class CustomerService
    {
        private readonly IDbContext _dbContext;

        public CustomerService(IDbContext dbContect)
        {
            _dbContext = dbContect;
        }
        public async Task<IList<CustomerWithOrdersProjection>> GetCustomersWithOrders(int id)
        {
            // hier die logik um die Customers mit Ordern zu laden

            var customerEntities = await _dbContext.Customers.Include(c => c.Orders).Where()...ToListAsync() // 

            IList<CustomerWithOrdersProjection> customers = new List<CustomerWithOrdersProjection>()
                // Schleife und hier die Werte der Einträge in customerEntities in customers übertragen
                // Automapper kann dies auch automatisch machen

            return customers;
        }
    }

Diesen Service kannst Du dann in der Action aufrufen.

Allgemein aber nochmals Hinweise:

  • Schau Dir mal die Konzepte in EF und ASP.NET Core an. Investier einfach mal 2 Tage in Doku lesen. Danach kommst Du auch viel schneller voran
  • Der Bezeichner "Customer" suggeriert ein Business Model. Entitäten haben meist ein "Entity" Suffix. Entitäten sind einfach keine Business-Objekte, sondern nur Daten-Objekte.
301 Beiträge seit 2009
vor 4 Jahren

Wie aber Papst schon sagte musst Du ordentlich die Schichten trennen.

Haha ich hab gelesen: Wie der Papst schon sagte ....

Musste herzlich lachen weil ich den Gedanken übertragen habe auf "das Volk in Schichten zu trennen". Ich dachte echt es ist eine Floskel.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 4 Jahren
...  
  
namespace Firmenname.Produkt.Services  
{  
    public class CustomerService  
    {  
        private readonly IDbContext _dbContext;  
  
        public CustomerService(IDbContext dbContect)  
        {  
            _dbContext = dbContect;  
        }  
        public async Task<IList<CustomerWithOrdersProjection>> GetCustomersWithOrders(int id)  
        {  
            // hier die logik um die Customers mit Ordern zu laden  
  
            var customerEntities = await _dbContext.Customers.Include(c => c.Orders).Where()...ToListAsync() //   
  
            IList<CustomerWithOrdersProjection> customers = new List<CustomerWithOrdersProjection>()  
                // Schleife und hier die Werte der Einträge in customerEntities in customers übertragen  
                // Automapper kann dies auch automatisch machen  
  
            return customers;  
        }  
    }  
  

Initalisiere ich den Service dann in der ASP.NET Anwendung?

Da registriert man ja eigentlich einen Kontext z.B. so:

    services.AddDbContext<SchoolContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

Also von wo aus würdest du deinen CustomerService erstellen?

Ich habe jetzt einmal das als Test.Common.Data:



    public class OrderContext : DbContext
    {
        public OrderContext(DbContextOptions<OrderContext> options) : base(options)
        {
        }

        public DbSet<Customer> Customers { get; set; }
        public DbSet<Orders> Orders { get; set; }
}

Nun erstelle ich eine ähnliche Service Funktion wie deine (mit GetAllOrders(), etc.), von wo initialisiere ich die jetzt?

16.806 Beiträge seit 2008
vor 4 Jahren

Der Service würde als Scoped* als im DI Container registeriert werden.
Dann kannst ihn in der Action als Ctor-Parameter empfangen.

*je nach Kontext auch Transient.
Aber auf keinen Fall als Singleton.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 4 Jahren

Ok danke, werde ich versuchen.

Ich bin gerade noch dabei das erstmal ohne ASP auszubauen.

Folgendes Problem beim Service:


        public async Task<IList<Customer>> GetAllCustomers()
        {
            var Customers = await this._dbContext.Customers.Include(p=>p.Orders).ToListAsync();

            return Customers;
        }

Abruf:


            var customers = ts.GetAllCustomers();
            customers.Wait();
...

Wenn ich das Include weglasse geht es wunderbar, wenn ich es drin habe kommt folgender Fehler:

Fehlermeldung:
InvalidOperationException: Lambda expression used inside Include is not valid.

16.806 Beiträge seit 2008
vor 4 Jahren

Wait() ist ein Anitpattern. Brauchst Du Wait() bei Tasks, dann machst Du was falsch.
async await falsch anzuwenden ist schlimmer als es nicht anzuwenden.

Der Fehler> Fehlermeldung:

Lambda expression used inside Include is not valid. kommt, wenn eine Eigenschaft kein Setter hat.
Wahrscheinlich hat Orders nur ein Getter.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 4 Jahren

Danke! Hab wirklich den Setter vergessen.

Ich rufe das aktuell noch aus einer Konsolen Anwendung auf. Deswegen das mit dem Wait()
Wäre es jetzt ein ASP Projekt im Controller, wäre der Aufruf ja innerhalb eines Tasks, da müsste es dann wie bei dir aufgerufen werden:

var p = await _eventDispatcher.Send(new GetUserProfileViewProjection(userId));

oder?

16.806 Beiträge seit 2008
vor 4 Jahren

Jo; aber auch mit Konsolenanwendungen kann man ordentlich async await umsetzen.

C# Main() Method - Async Main return values

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Der Service würde als Scoped* als im DI Container registeriert werden.
Dann kannst ihn in der Action als Ctor-Parameter empfangen.

Also ich habe jetzt meinen Service mit Interface:


public interface ICustomerService{
      Task<IList<Customer>> GetAllCustomers();
}

Den Service registriere ich dann in der ASP Anwendung:


services.AddScoped<ICustomerService, CustomerService>();

Wie kann ich jetzt möglichst einfach vom Controller darauf zugreifen?

16.806 Beiträge seit 2008
vor 3 Jahren

Siehe Blick in die Doku
Dependency injection into controllers in ASP.NET Core
Bitte vorher selbst mal die Dokusuche verwenden 😉
Ist manchmal schneller gesucht als ein Beitrag geschrieben.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Perfekt danke!

Falls jemand das gleiche Problem hat, mit Parameter:


services.AddTransient<ICustomerService>(s => new CustomerService(new CustomerContext()));

Noch eine Frage zu deinem Beispiel Code,
du erstellst den View dann auch in einer extra "Schicht" oder?
Also in der ASP Anwendung ist so zusagen außer den jeweiligen Funktionsaufrufen fast gar keine Logik?

16.806 Beiträge seit 2008
vor 3 Jahren

services.AddTransient<ICustomerService>(s => new CustomerService(new CustomerContext()));

Das ist nicht gut.
Der Parameter sollte genauso registriert werden. Zudem solltest Dir überegen ob Du wirklich Transient brauchst oder doch nicht Scoped (vor allem beim Kontext).
Steht auch in der Doku. Nimm Dir bitte (endlich) Zeit sie zu lesen.

du erstellst den View dann auch in einer extra "Schicht" oder?

Was meinst Du damit?
Views sind an für sich ohnehin in ASP.NET Core entkoppelt (außer Du verwendest Razer Pages).

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Du meinst schon das hier oder?
https://docs.microsoft.com/de-DE/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1

Scoped "lebt" dann solange wie der Client verbunden ist, ist dann mit Context schlauer?
Transient würde dann bei jedem Refresh einen neuen Context erstellen, hab ich das richtig verstanden?

P
441 Beiträge seit 2014
vor 3 Jahren

Ein Client ist nicht verbunden in dem Sinne.
Bei HTTP gilt, die Verbindung des Clients ist ein Request. Danach ist er nicht mehr verbunden.

Scoped lebt über genau einen Request. Innerhalb eines Requests bekommst du immer die gleiche Instanz der Klasse.
Bei Transient bekommst du jedes mal eine neue Instanz.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Ok, also ist scoped bzgl. Performance besser geeignet oder?

16.806 Beiträge seit 2008
vor 3 Jahren

Puh.. Du magst wirklich keine Dokus lesen, oder?
Und ich mag Dir nicht die Doku vorlesen 😃

-> Eine Action kann mehrere Services haben, die jedoch prinzipiell technisch nicht nur an der Action selber sondern an vielen Stellen des Requests verwendet werden können.
-> Das gleiche gilt für den Kontext: ein Kontext kann / muss von vielen Services verwendet werden, zB. wegen Context Shared Informations.

Es kann daher zu Race Conditions, wenn jeder Service seinen eigenen Context bekommt bzw. wenn solche Dependencies nicht richtig registriert sind.
Es geht also nicht um Performance, sondern einfach um Logik. Es kommt daher durchaus an, wie man programmiert.

War nun auch mein letzter Hinweis dazu, da ich wie gesagt Dir nicht die Doku vorlesen mag.
Ist nicht Sinn der Sache.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Ich versteh es nicht ganz 😄

Also den Context dann als Singleton und den dann an den Service (Scoped) übergeben sodass es insgesamt nur einen Context gibt?

16.806 Beiträge seit 2008
vor 3 Jahren

Les Dir durch was ein Singleton ist und Du weißt, warum das noch eine schlechtere Idee ist...
Du wirst nicht drum herum kommen Sachen zu lesen.

Liest Du nicht, dann knallt Dir alles - zurecht - um die Ohren.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Ich weiß immer noch nicht genau was du meinst:
Ist der Kontext dann ein bereichsbezogenen Dienst?

Soll man den dann so aufrufen:

var serviceContext = services.GetRequiredService<MyScopedService>();

?

In diesem Beispiel haben sie es so gemacht:
https://docs.microsoft.com/de-DE/aspnet/core/data/entity-framework-6?view=aspnetcore-3.1

services.AddScoped<SchoolContext>(_ => new SchoolContext(Configuration.GetConnectionString("DefaultConnection")));

Wenn ich das mit dem CustomerContext mache müsste ich es ja dem Service noch übergeben, wie mach ich das dann, so wie mit dem serviceContext?

services.AddTransient<ICustomerService>(...));
301 Beiträge seit 2009
vor 3 Jahren

Wenn die Abhängigkeiten deines Services im DI Container registriert sind, dann werden die Laufzeit automatisch aufgelöst. D.h. du musst nichts explizit übergeben.

Wenn also sowohl Context als auch Service beide als Typen im DI Container registriert sind werden die Abhängigkeiten zur Laufzeit auch vom DI Container automatisch erzeugt / wiederverwendet

16.806 Beiträge seit 2008
vor 3 Jahren

In diesem Beispiel haben sie es so gemacht:

services.AddScoped<SchoolContext>(_ => new SchoolContext(Configuration.GetConnectionString("DefaultConnection")));  

Jo, und Du verstehst es nicht, weil Du Dich nicht wirklich auch nur eine Minute mit dem DI Framework von MS beschäftigt hast.
Allein die erste Seite der DI Docs würden 50% der Fragen beantworten.

Der Context selbst ist eine Klasse, kann daher einzeln - als Scoped - registriert werden.
Dem Initializer muss aber geholfen werden, weil der Kontext einen String - die Connection-Details - erwartet.
Strings können aber nicht (so ohne weiteres) registriert werden, sodass es entweder die Initializer Registrierung gibt wie hier gezeigt wird oder man registriert zusätzlich ne ConfigOption.

Ein einer produktiven Welt würde man das so machen

services.Configure<SchoolContextOptions>(Configuration.GetSection("Database"));
services.AddScoped<SchoolContext>();
services.AddScoped<ICustomerService>();

Man könnte zwar auch die ConnectionString Section nehmen; oft gibt es aber zusätzlich notwendige Settings.

Einzige Lösung: les endlich die Fundamentals. Das Forum wird Dir das Zeug nicht programmieren.
ASP.NET Core fundamentals

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Vielen Dank KroaX, funktioniert!

Leider ist das ganze auch nach dem Lesen der Doku für Anfänger nicht so trivial 😉

Vielen Dank an alle!

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Hi,

nochmal eine kleine Frage ob ich es richtig verstanden hab:
Ich habe diese zwei Objekte, mit denen arbeite ich z.B. in meinem Service, Context, etc.


    public class Order
    {
        public int Id { get; set; }
        public string Title { get; set; }

        public Customer Customer { get; set; }
    }

    public class Customer
    {
        public int Id { get; set; }
        public string Name_Last { get; set; }
        public string Name_First { get; set; }
        public List<Order> Orders { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return Name_Last + ", " + Name_First;
            }
        }
    }


Für die erstelle ich dann jeweils ein ViewModel z.b:


    public class CustomerViewModel
    {
        public int Id { get; set; }
        public List<OrderViewModel> OrderViewModels { get; set; }

        [Display(Name = "Full Name")]
        public string FullName{ get;set; }
       
        public CustomerViewModel(Customer customer){
             this.FullName = customer.FullName;
             this.Id = customer.Id;
             ...
        }
    }

Darin habe ich dann nur was der Anwender in der Asp.net Anwendung sehen muss?

Im Controller mappe ich die Customer Objekte auf die ViewModels (bzw. AutoMapper) und gebe die dann z.B. an den View weiter.
Ist das soweit richtig? Gibt es da noch eine bessere ERklärung warum man das so macht? Bei allen ASP.NET Tutorials greifen die direkt auf z.B. Customer zu.

16.806 Beiträge seit 2008
vor 3 Jahren

Bei allen ASP.NET Tutorials greifen die direkt auf z.B. Customer zu.

Bei meinem nicht 😉

Tutorials verfolgen den Sinn, dass sie einen gewissen Mechanismus zeigen. Ein Tutorial wird niemals alle Aspekte an eine Software Architektur abdecken, sondern das Wesentliche zum Thema zeigen.
Daher siehst Du in Dokumentationen, Tutorials und Co stehts vereinfachte Art und Weisen, die Du dann in der echten Welt entsprechend umsetzen musst.

Der Sinn solcher Modelle und Services ist die Trennung von Verantwortlichkeiten und Abhängigkeiten, sowie der Wiederverwendbarkeit.
Es gibt verschiedene Wege dafür; es gibt den Modell-Weg, der sehr sehr weit verbreitet ist und den Du hier verfolgst, aber es gibt auch völlig andere Wege - zB. CQRS mit Projektionen (mein Favourite).

Es zwingt Dich niemand das strikt durchzusetzen, wenn Du auch an den ein oder anderen Stellen effizienter bist, wenn Du direkt die Instanzen aus der Quelle verwendest - musst dann eben mit den Konsequenzen leben und ggfls. doch nochmal Hand anlegen, solltest Du eine entsprechende Zusatzklasse brauchen.
Es gibt hier nicht "den perfekten Weg"; manchmal sind das eben auch Kompromisse.

ASP.NET hat prinzipiell keinen andere Architekturbasis als zB WPF - und auch dort verwendet man entsprechende Modelle für Views.
Und auch bei WinForms oder ganz anderen Sprachen ist das Modellieren Model-To-View üblich.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Hi,

sorry das ich den Thread nochmal rausgrabe, habe aber zur Eingangsfrage noch mal ein Problem.


    public class Order
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int CustomerId { get; set; }
        public Customer Customer { get; set; }
    }

    public class Customer
    {
        public int Id { get; set; }
        public string Name_Last { get; set; }
        public string Name_First { get; set; }
        public List<Order> Orders { get; set; }
    }

Ich hätte gerne einen JSON Exporter der Objekte exportiert, dabei entsteht aber eine Zirkularität Order <-> Customer. Und mit den Include() wird es bei immer mehr Objekten (z.B. Order hat List<Article>,...) auch immer komplexer.

Jetzt meine Frage, wie designe ich das am Besten? Lasse ich bei den Models dann die ganzen Lists (List<Order> bei Customer,...) weg und verweise nur noch im Order-Objekt auf die Customer Id, sdass ich da keine Zirkularität bekomme?

In der ASP.NET Anwendung habe ich außerdem mehrere ViewModels, z.B.:


    public class CustomerViewModel
    {
        public int Id { get; set; }
        public List<OrderViewModel> OrderViewModels { get; set; }

        [Display(Name = "Full Name")]
        public string FullName{ get;set; }

        public CustomerViewModel(Customer customer){
             this.FullName = customer.FullName;
             this.Id = customer.Id;
             ...
        }
    }

Fülle ich z.B. hier die OrderViewModels dann im Controller oder sollte das eine Schicht darunter passieren? Habe allerdings nur im Controller Zugriff auf den Service.


var model = new CustomerViewModel(_service.GetCustomerById(Id));
...
model.OrderViewModels.Add(_service.GetOrdersFromCustomerId(Id));
...

16.806 Beiträge seit 2008
vor 3 Jahren

ViewModels sind Teil der UI und werden daher in der Action erzeugt.

Jetzt meine Frage, wie designe ich das am Besten? Lasse ich bei den Models dann die ganzen Lists (List<Order> bei Customer,...) weg und verweise nur noch im Order-Objekt auf die Customer Id, sdass ich da keine Zirkularität bekomme?

Typischer Fall von: kommt drauf an.
Kann ich so nicht beantworten.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

ViewModels sind Teil der UI und werden daher in der Action erzeugt.

Ok, ja dann mache ich das jetzt so, dass ich diese Include möglichst weglasse und die Objekte nur in ViewModels zusammensetze, danke!

16.806 Beiträge seit 2008
vor 3 Jahren

Ich bin derzeit dabei einen Blogbeitrag zum Thema CQRS, AutoMapper und Projektionen zu erstellen, der prinzipiell das gesamte Handling für Include und Co behandelt.
Das erleichtert die Arbeit mit solchen Situationen ungemein (und auch der neue Code von mycsharp.de basiert darauf).

Ich verlinke ihn, sobald er fertig ist.

Hinweis von Abt vor 3 Jahren

Die eigentliche Frage wurde schon vor Ewigkeiten beantwortet.
Im Hinblick auf [Hinweis] Wie poste ich richtig? schließe ich den Thread um weitere Endlosfragen zu vermeiden.

Thema geschlossen