Laden...

Fragen zum Entity Framework und Architektur

Erstellt von elTorito vor 9 Jahren Letzter Beitrag vor 9 Jahren 9.121 Views
elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren
Fragen zum Entity Framework und Architektur

verwendetes Datenbanksystem: SQL 2008R2
VS2012

Hi,

ich hab mich einige Zeit mit ADO .NET beschäftigt, und mir eine Anwendung gebastelt welche folgende Struktur hat:

* Ein, ich nenne es mal "Modul", bestehend aus 4 DLL's:
Artikel.BusinessLogic
Artikel.Contracts
Artikel.Services
Artikel.GUI

* Ein WinForm Client
* Shared Business Layer
* Service Access Layer

* Zyan Server
* Data Access Layer

Der Ablauf ist wie folgt:

  • Der WinForm Client kennt Artikel.GUI, und Artikel.Contracts und Shared Business Layer.
    Im WF Client wird die Artikel.GUI geladen, das zu ladende Objekt "Artikel" ist in Artikel.Contracts definiert. Um den Artikel zu laden ruft der WF Client eine Methode im SharedBusiness Layer auf (der auch Artikel.Contracts kennt).

Im Shared Business Layer wird ein Aufruf an Service Access Layer abgesetzt. der Service Layer verbindet mit dem Zyan Remoting Server.

Der Zyan Server verweist auf Artikel.Services und Artikel.Contracts, und ruft die passende Methode in Artikel.Service auf.

Artikel.Service wiederrum verweist auf Artikel.Business, und in Artikel.Business wird dann die passende Methode im Data Access Layer aufgerufen (welcher auch Artikel.Contracts kennt).


return ArtikelDAL.GetProduct(productID);

Es wird also ein Objekt vom Typ Artikel durchgeschleift bis zum WinForm Client.

Die ADO Geschichte funktioniert so wie ich es wollte. Also Grundgerüst aufgebaut, also kann ich anfangen damit meine Anwendung etwas komplexer zu gestalten, habe dann überlegt ob es ein weg gibt der die Sache etwas beschleunigen kann, und habe dann immer wieder gelesen das wenn die Anwendung komplexer wird, man einen O/RMapper nehmen sollte . Da ich gerne MS Hausmittel benutze also angefangen mich ins Entity Framework einzulesen und zu testen.

Gar nicht so einfach da den passenden Einstieg zu finden. Also erstmal Schnelleinstieg, und dann diverse Beispiele von Codeplex, Codeproject ausprobiert, aber so richtig richtig den Einstieg finde ich nicht.

Nun sehe ich bei den EF Beispielen immer wieder das die Entitäten durchgeschleift werden bis zur GUI. Bzw. die UI ein Verweis legt auf da wo sich das Entität Daten Modell befindet.

Nun weiß ich nicht ob ich meine Anwendung "remodellieren" soll, oder ob ich die Entitäten in "meine" Objekte umwandeln soll.

Hat es einen Nachteil wenn ich meinen Data Access Layer , der mir derzeit "eigene Objekte" (DataTables werden in Objekte konvertiert") ersetze durch das Entity Framework, und mir dann die Entitys in Objekte umwandeln lasse?

Ich würde nun folgendes versuchen:
Im Artikel.Contracts ist derzeit mein Objekt Artikel definiert und Artikel.Contracts stellt auch ein Interface bereit.

In Artikel.Contracts das Entität Model ablegen, und im Artikel.Business dann die Umwandlung von der Entität ins Objekt.

Ich bin mit den ganzen Begriffen noch nicht so vertraut, wie gesagt, finde nur schwer den Einstieg.

Wenn ich in der Artikel.Contracts ein Datenmodell (anhand meiner Tabellen) erstelle, wird ja auch automatisch das Objekt Artikel erstellt, welches dann Bestandteil von ein DatenContext ist. So wie auch die Listen / Collections der Verknüpfungen.

Die meisten UI's scheinen den DatenContext zu binden, wäre das der richtige Weg? Als dem WIn Client den DatenContext zur Verfügung stellen statt einer "dummen" Objekt Liste?

Vielleicht hat noch jemand ein Tipp für den Einstieg ins EF , über das MSDN will mir das nicht gelingen, es scheint auch stark von den Anforderungen abzuhängen wie man da vorgeht.

Was ich gerne möchte ist die Datenbank, wie gehabt, mit dem SQL Management Studio modellieren, und meine Anwendung soll die Datenmodelle anhand bestehender Tabellen erzeugen.

Und jetzt? Entität in Objekte umwandeln und alles andere lassen wie es war? Oder DatenContext bekannt geben?

Beim MSDN (z.B. hier) scheitere ich schon daran das ich dort nicht sehe welches EF gerade besprochen wird, und scheinbar macht es auch ein riesen Unterschied ob man EF 4, 5 oder 6 einsetzt.

Wenn ich richtig verstanden dann habe ich meine ersten Gehversuche mit EF 4 gemacht (nach dem erstellen der EDMX wurde zumindest EntityFramework v4.030319 als Verweis hinzugefügt)

Vielleicht hat jemand noch ein paar Gute Links auf Lager und evtl. paar Antworten auf mein Chaos

Danke.

W
955 Beiträge seit 2010
vor 9 Jahren

Hallo,

vllt mal erste Gedanken:

* Das EF kann POCO's erzeugen (z.B. durch Code-First-Strategie), d.h. diese Entityobjekte haben keinen Bezug zum Entity Framework und können daher als Modellobjekte verwendet werden. D.h. als erste Verbesserung könnte darin bestehen dass Du Deine Modellobjekte direkt mit dem EF persistieren lassen kannst.
* Um zu verhindern dass das EF durch die ganze App hindurchgeschleift wird bietet sich das Repository-Pattern an das das Data Access Layer von den oberen Schichten insofern abtrennt dass diese kein EF referenzieren.

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Hallo witte,

danke für die Antwort.

Mit Repository Pattern ist so etwas hier gemeint?

Ich hab nun mehrfach gelesen das das Entity Framework schon so eine Art "Repository" sein soll, und das man deswegen nicht unbedingt dieses Muster braucht. Aber verstanden warum habe ich nicht.

Ich habe nun mal folgendes ausprobiert:

Erstmal EF 6 besorgt.

Und damit folgende Struktur gebastelt:

Eine neue Library: Mn.DomainModels.dll (wie in dem Beispiel oben beschrieben)

Library Person.Contracts.dll beinhaltet die (aus der Datenbank generierte EDMX Datei, in mein Fall Person.edmx), wie auch oben in dem Beispiel beschrieben, habe ich die Person.tt ausgelagert in Mn.Domainmodels.dll und die Person.Context.tt angepasst mit Verweis auf Mn.Domainmodel.dll

Auch in der Person.Contracts.dll befindet sich ein Interface IPersonService:


namespace Mm.DomainModel
{
    public interface IPersonService
    {
        /// <summary>
        /// Gibt eine bestimmtes Person anhand eines Schlüssels zurück.
        /// </summary>
        XUser GetXUser(Guid productID);

        bool AddUser(XUser user);
    }
}

Weiter habe ich eine Person.Business.dll welche auf Mn.DomainModels.dll und auf DataAccessLayer.dll verweist.

Eine Person.Service.dll welche auf Mn.DomainModels.dll und Person.Business.dll verweist.

Eine Person.WindowsGUI.dll welche auf Mn.DomainModels.dll und SharedBusiness.dll verweist.

In der DataAccessLayer.dll habe ich dann z.B. folgenden Code:


namespace elTorito.DataAccessLayer
{
    public class PersonDAL
    {
        public static bool AddUser(XUser varUser)
        {
            int changesSaved = 0;

            // Create the database context
            using (var dbContext = new PersonEntities())
            {
                // Construct a User object with the correct properties
                var user = new XUser
                {
                    UserName = varUser.UserName,
                    FirstName = varUser.FirstName,
                    LastName = varUser.LastName,
                    EmailAddress = varUser.EmailAddress
                };

                // Add the User object to the database context's Users collection
                dbContext.XUsers.Add(user);

                try
                {
                    // Save the changes to the database, and record the number of changes
                    changesSaved = dbContext.SaveChanges();
                }
                catch (System.Data.Entity.Validation.DbEntityValidationException dbEx)
                {
                    Exception raise = dbEx;
                    foreach (var validationErrors in dbEx.EntityValidationErrors)
                    {
                        foreach (var validationError in validationErrors.ValidationErrors)
                        {
                            string message = string.Format("{0}:{1}",
                                validationErrors.Entry.Entity.ToString(),
                                validationError.ErrorMessage);
                            // raise a new exception nesting
                            // the current instance as InnerException
                            raise = new InvalidOperationException(message, raise);
                        }
                    }
                    throw raise;
                }
            }


            // Return a bool based on whether any changes have been stored
            return changesSaved >= 1;
        }

        public static List<XUser> GetAllUsers(){
            // Create the database context
            using (var dbContext = new PersonEntities())
            {
                return (from c in dbContext.XUsers
                            select c).ToList();
            }
        }

    }

DataAccessLayer.dll verweist auf Person.Contracts.dll (wo das EDMX File liegt) und auf Mn.DomainModel.dll

Okay. Weiter...
Der Zyan Remoting Server verweist auf Mn.DomainModels.dll und Person.Service.dll

Dann habe ich meinen WinForm Client welcher auf Person.Windows.GUI.dll verweist, SharedBusiness und ServiceAccess welche beide Mn.Domain.Model.dll kennen.

Auf Client Seite ist also nur Person.Windows.GUI.dll und Mn.DomainModel.dll bekannt.

Der Zyan Server kennt Mn.DomainModel.dll und Person.Service.dll (wo das Interface in Mn.DomainModel bereitsteht)

Person.Service.dll kennt den Person.Business.dll und Mn.DomainModel.dll

Person.Business.dll kennt Mn.DomainModel.dll und den DataAccessLayer.dll

Soweit so gut. Ändere ich nun mit dem SQL Management Studio die Tabelle und füge eine Spalte hinzu, gehe ich in meine Person.Contract und rufe "Model aus Datenbank aktualisieren" auf. Anschliessend in Mn.Domainmodel.dll Rechter Mausklick auf person.tt und sage "Benutzerdefiniertes Tool ausführen" (was auch immer das bedeuten mag) und die XUser Klasse wird aktualisiert.

So habe ich das Problem das beim Speichern der Fehler kommt dass die Klasse User nicht Serialisierbar ist. Okay, also in Mn.DomainModel.dll noch folgendes reingeschrieben:


namespace Mm.DomainModel
{
    public interface IPersonService
    {
        /// <summary>
        /// Gibt eine bestimmtes Person anhand eines Schlüssels zurück.
        /// </summary>
        XUser GetXUser(Guid productID);

        bool AddUser(XUser user);
    }

    [Serializable]
    public partial class XUser
    {
    }
}

Führe ich das Beispiel so aus, fügt die Methode AddUser (im DAL mir einen neuen benutzer in die Tabelle ein).

Habe ich die Schichten so Sauber genug getrennt?
Welche Nachteile kann dieses Gerüst nun haben?

So wie es nun ist könnte ich jeder zeit die POCO'S Objekte übernehmen und den DAL austauschen?
Weil Person.Business.dll ja nur den DAl kennt:


 namespace Person.BusinessLogic
{
    public class PersonManager
    {
        public static bool AddUser(XUser user)
        {
            return XUserDAL.AddUser(user);
        }
        public static List<XUser> GetAllUsers()
        {
            return XUserDAL.GetAllUsers();
        }
    }
}

Hmm. Okay. Habe gerade mein Beispiel um ein GetAllUsers erweitert:
im DAtaAccessLayer:


public static List<XUser> GetAllUsers(){
            using (var dbContext = new PersonEntities())
            {
                return (from c in dbContext.XUsers
                            select c).ToList();
            }

Knallt es mit > Fehlermeldung:

Die Assembly "EntityFrameworkDynamicProxies-Mm.DomainModel kann nciht gefunden werden was scheinbar einer fehlenden Deserialisierung die Folge ist...

Also nochmal zurück auf Anfang und versuchen das mit dem Repository zu verstehen, oder De/Serialisierung behandeln?

Ai ai ai .... X( X(

16.842 Beiträge seit 2008
vor 9 Jahren

Beim Repository Pattern teilen sich mehrere Repository Instanzen (UserRep, BlaBlaRep, FooRep), einen gemeinsamen Context.
In jeder Methode den Context zu erstellen ist unpraktisch, uneffizient und im Rahmen der Contextzueghörigkeit und Lebenszyklus von Entitäten auch falsch.
Repositories sind quasi die letzte Schnittstelle vor der Datenbank, die sich auch schnell und einfach testen lässt -> Dependency Injection.
Daher: Context in den Konstruktor.

ToList sollte man ebenfalls vermeiden - außer man weiß genau, was man tut; Stichwort Materialisierung und LazyLoading.
Ich bezweifle, dass die Fehlermeldung von der Serialisierung kommt. Da muss man sich - im Normalfall - sowieso keine Gedanken machen, wenn man mit dem EF arbeitet.

Im Repository sollte keine Abhängigkeitslogik der Entitäten gesetzt oder geprüft werden.
Das sollte ein ServiceLayer (BL) übernehmen. Reps sind dazu da, Daten aus der DB zu holen und Daten zu setzen - aber keine Businesslogik.

Und: insgesamt ist der ganze Quellcode alles andere als gut zu bezeichnen und zeigt, dass Dir da die Grundlagen fehlen 😉

P
1.090 Beiträge seit 2011
vor 9 Jahren

Da entspricht jetzt nicht ganz dem was ursprünglich zum Repository Patter geschrieben wurde.
Martin Fowler:Repository

Das Repository kann als Mediator zwischen Mapping und der Domaine dienen.
In dem Fall ist es auch nicht Falsch Abhängigkeitslogik von Entitäten zu setzen.

Meiner Meinung nach, sollte man an dem Punkt vielleicht auch differenzieren. Zwischen Datenzugriffslogik und Business Logik.
Business Logic sollte meine Geschäftsprozesse abbilden, gehört für mich in den BL.
Datenzugriffslogik den Zusammenhang der Daten (Tabellenstrukturen usw.), was eher in den DAL gehört.

Den Context über IOC an das Repository zu geben, halte ich auch für eine bessere Lösung.
Aber da muss man ach versteh was man macht. Im Zweifel halte ich es für besser, einen neuen Context zu erzeugen (Performance Probleme) als auf den gleichen Context in unterschiedlichen Repositorys zu arbeiten (Exceptions).

Ich würde den Code jetzt auch nicht als gut bezeichne. Besser geht halt meistens. (Und ja hier ist es recht einfach eine Bessere Lösung zu finden.) Aber ich hab auch schon bei weiterem schlechteren Code gesehen. Die Frage ist halt was man als Grundlage sieht, ich fände auch schon wenn jeder Entwickler wenigstens die GoF Pattern kennen würde.

MFG
Björn

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

W
955 Beiträge seit 2010
vor 9 Jahren

Im Zweifel halte ich es für besser, einen neuen Context zu erzeugen (Performance Probleme) als auf den gleichen Context in unterschiedlichen Repositorys zu arbeiten (Exceptions). Hallo, wenn man generische Repositories vewendet ist es meist üblich das jedes Repository einen Entitätstyp abbildet/versorgt. Wenn man nun in einer Arbeitseinheit Entities verschiedener Typen zusammen speichern will kann man nicht jedem Repository ein eigenen Context zuweisen sondern alle Repositories derselben Arbeitseinheit müssen denselben Kontext verwenden damit die Objektbeziehungen gespeichert werden können. Deshalb ist es hier besser ein UnitOfWork o.ä. zu verwenden das seinen Kontext in den Repositories injiziert oder entsprechende Facilities des DIC zu nehmen wenn vorhanden.

P
1.090 Beiträge seit 2011
vor 9 Jahren

@witte
Also wenn ich den gleichen Context benutzen muss, weil eine Abhängigkeit zwischen den Objekten besteht. Habe ich da eine sehr enge Kopplung. Ich kann also nicht die Repositorys alleine Verwenden, sondern muss sie alle zusammen Verwenden? Dann kann man auch direkt auf den Context Arbeiten.

Also der Context ist dein Arbeitsbereich (Unit of Work), das Repository kennt ihn und seine Abhängigkeiten. Die Klasse die das Repository benutzt, soll genau diese Abhängigkeiten nicht kennen. Deshalb sollten die Abhängigkeiten auch im Repository aufgelöst werden.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

16.842 Beiträge seit 2008
vor 9 Jahren

Die Klasse die das Repository benutzt, soll genau diese Abhängigkeiten nicht kennen. Deshalb sollten die Abhängigkeiten auch im Repository aufgelöst werden.

Seh ich völlig anders - deswegen auch mein Post oben.
Die Business-Logik sollte die logischen Abhängigkeiten kennen und setzen. Daher sollte man das auch in der BL und nicht im DAL abdecken.

Der DAL ist dazu da, dass die Daten unabhängig vom Speicherelement gespeichert werden.
Als Beispiel die BenutzerTabelle in einer SQL DB und die Shared Sessions in einer RedisDB.
Die Verknüpfung zwischen Session und Benutzer findet aber in BL statt - und nicht wirklich im DAL. Hat auch damit was zutun, welche Daten in welchem Umfang geladen werden müssen. Das weiß der BL, aber nicht der DAL.

Im DAL haste meinetwegen Logik zur konsistenten Datenhaltung - aber mehr auch nicht.

Ob das nun ein Fowler anders sieht: is mir wurst.
Ich seh das so, wie es im realen Leben effizienter ist 😉

Also wenn ich den gleichen Context benutzen muss, weil eine Abhängigkeit zwischen den Objekten besteht. Habe ich da eine sehr enge Kopplung. Ich kann also nicht die Repositorys alleine Verwenden, sondern muss sie alle zusammen Verwenden? Dann kann man auch direkt auf den Context Arbeiten.

Das kommt - leider - auf die verwendete DB bzw. den Provider an.
Beim EF bist Du durch die Context-Abhängigkeit stark gekoppelt. Das liegt aber an gewissen Funktionen wie ChangeTracking und dabei kannst Du darauf nicht verzichten. Ansonsten musst Du immer de- und reattachen.
Hast Du solche Features nicht, dann hast Du auch keine enge Kopplung.

P
1.090 Beiträge seit 2011
vor 9 Jahren

@Abt
Du kannst natürlich deine Klassen so benennen wie du möchtest. Und wenn man ein Patter verwendet was eher einer Remote Facade entspricht und es dann Repsitory nennst ist es für mich auch ok.

Tatsache ist, das wenn es Abhängigkeiten zwischen zu Speichernden Daten gibt. Dieses irgendwo im Quellcode geschehen muss. Ob diese im DAL oder BL geschieht, bleibt jeden selbst überlassen.

Meines Erachtens sollten aber Abhängigkeiten möglichst sichtbar sein.

Es mag ja für dich Effektiv sein z.B. erst Kunde und dann Rechnung mit dem Selben Context aufzurufen.
Für mich ist es Verständlicher, das Rechnung eine Kunden ID erwartet.

Meines Erachtens sollte man erstmal so Programmieren, das möglichst viele den Quellcode verstehen und benutzen können. Und sich erst danach Gedanken zur Optimierung machen.

p.s. die Starke Koppelung im EF kann man auflösen wenn man das Repoitory Pattern verstanden hat.

p.p.s. das Leute die OOP weniger verstanden haben als man selbst, kommt immer wider vor. Das ist aber noch lange kein Grund jemanden vorzuwerfen, dass er die OOP Grundlagen nicht verstanden hat. Mache ich ja auch nicht. OK nicht direkt 😉

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

W
955 Beiträge seit 2010
vor 9 Jahren

Hi,

Also der Context ist dein Arbeitsbereich (Unit of Work), das Repository kennt ihn und seine Abhängigkeiten. Die Klasse die das Repository benutzt, soll genau diese Abhängigkeiten nicht kennen. Deshalb sollten die Abhängigkeiten auch im Repository aufgelöst werden. Wie soll ich das verstehen? Wenn man beispielsweise eine Artikelliste hat und packt ein Artikel in seinen Einkaufswagen, werden dann beide Entitäten in deinem Repository behandelt?

P
1.090 Beiträge seit 2011
vor 9 Jahren

@witte
Grundlegend schon.
Das Repository würde Einkaufswagen und Artikelliste kennen und die Passenden Einträge in die Datenbank machen.
Der BL würde nur wissen das ein Artikel in den Einkaufswagen gepackt wurde. Welche Einträge dafür in die Datenbank gemacht würden sollten ihn nicht interessieren.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

W
955 Beiträge seit 2010
vor 9 Jahren

ich abstrahiere so etwas durch eine ServiceSchicht die auf die Repositories aufsitzt. Es lässt sich darüber streiten ob das des Guten zuviel ist, ich möchte jedoch Redundanzen vermeiden. Ein Service kennt seine Repositories, sucht sich die Daten zusammen und gibt es an die darüberliegende Applikationsschicht (Controller o.ä.) weiter. Die Repositoryschicht ist sehr dünn und hat nur die Aufgabe der Abstraktion des DAL.

16.842 Beiträge seit 2008
vor 9 Jahren

witte, ich stimm Dir da voll und ganz zu.
Sowas gehört eben in den BL und nicht in den DAL.

P
1.090 Beiträge seit 2011
vor 9 Jahren

@Abt und witte
Und um ganz ehrlich zu sein mir ist es S... egal, wie ihr eure Klassen nennt. Solange die Konzepte der Datenkapselung, anständig vermittelt werden. Und ob es im BL oder DAL geschieht ist ehrlich gesagt auch nebensächlich.

Hinter den Repository Pattern, wie es vorgestellt wurde, steckt einfach ein Grundlegende Idee, diese kann man verstehen oder nicht.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

16.842 Beiträge seit 2008
vor 9 Jahren

Klar, Du kannst in Deinem BL ebenfalls zB für die Services den Repository Pattern verwenden - spricht nichts dagegen.
Aber Deine Beschreibung oben ist definitiv BL und DAL vermischt; und dat is nisch jut!

P
1.090 Beiträge seit 2011
vor 9 Jahren

Vielleicht einfach mal ein Beispiel aus der Praxis.
Bei meinen aktuellen Projekt geht es um Maschinen, die Sachen Produzieren, die Maschinen Arbeiten größtenteils mit Textdateien. Über Name und Position in der Textdatei ist z.B. das Programm was abgearbeitet werden soll eindeutig. Ein altes VB6 Projekt arbeitet grundlegend mit eine Access DB hier werden neue Programme angelegt und bei Bedarf der Maschine zugewiesen (In die Textdatei geschrieben). Die Neuentwicklung in .Net verwendet eine MSSQL Datenbank (und für einen Kunden ein MySQL Datenbank, Sonderwunsch), hier werden Berechnungen zur Optimierung der Programme angestoßen. Langfristig soll alles auf die SQL Datenbanken umgestellt werden.

Die Reps definieren eine gemeinsame Schnittstelle dir mir eine DomainModel vom Programm zurückliefert. Der BL Arbeitet nur auf dem DomainModel und wie die Reps die Daten speicher oder Mappen ist ihm völlig egal. Grundlegend habe ich intern 3 verschiedene Implementierungen der Reps. Für die Textdatei, die Access DB und die SQL DBs (hier tausche ich einfach den Context im Rep aus).

Wenn ich nun die Abhängigkeit der Entitäten im BL setzen und prüfen würde hätte ich ein Problem, die gibt es bei der Textdatei nicht. Ich müsste im BL also immer Prüfen wo die Daten herkommen und dann bei Bedarf die Abhängigkeiten setzen. Wenn ich die Abhänigkeiten aber im Rep auflöse, ist das meinem BL vollkommen egal. Er arbeitet auf dem DomainModel und der Rest interessiert ihn nicht. Wie so auch ab ich die Daten in einer Textdatei oder DB speichere sollte nichts mit meiner Geschäftslogik zu tun haben.

Ich sehe da jetzt auch keine Vermischung von BL und DAL sondern eine Trennung. Die halt nicht gegeben ist, wenn der BL die Abhänigkeiten der Entitäten unter einander kennt und sie setzen soll.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Hi,

danke für eure Antworten, die mir zeigen das ich so einiges nicht verstanden habe 🙂

Und: insgesamt ist der ganze Quellcode alles andere als gut zu bezeichnen und zeigt, dass Dir da die Grundlagen fehlen 😉

Bezogen auf das EF oder allgemein mein "Konstrukt" mit den diversen Dll's?

Hinter den Repository Pattern, wie es vorgestellt wurde, steckt einfach ein Grundlegende Idee, diese kann man verstehen oder nicht.

Was könnte helfen diese Idee zu verstehen? 🤔

16.842 Beiträge seit 2008
vor 9 Jahren

Die Idee ist: die darüber liegende Schicht / Anwendung interessiert nicht, wo die Daten liegen.
Das kann eine SQLDB sein, eine XML, oder eine MongoDB - völlig egal. Das Repository kümmert sich darum, dass die Daten (NICHT die Logik) so in der Anwendung ankommen, wie sie die Schnittstelle definiert.

Ob man alles in DLLs trennen muss ist oft auch Geschmackssache.
Ich machs immer weniger, da es im Web sowieso völlig egal ist. Es hilft aber dabei, dass die Schichten eingehalten werden.

Das Konstrukt selbst finde ich das schlimme, da es quasi alle Dinge, die man nicht tun sollte, zeigt 😉

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Hallo Abt,

die Idee meines "Konstrukt" war zu versuchen "Module" zu isolieren.

Meine Struktur (Ohne EF) ist derzeit folgende:

  1. Client (in mein Fall WinForm, könnte aber auch Web, Phone, sonstwas sein)
  2. Shared Business Layer, (Pro Forma, falls ich auf Clientseite doch noch irgendwas behandeln muss)
  3. Services Access Layer

Der Service Access Layer soll den Zugriff auf den Service Anbieter Managen, also in mein Fall der Zyan Server, es könnte aber auch eine Lokale Beispiels Datenbank sein, oder ein WebService

  1. Zyan Server
  2. Business Layer (holt sich die Daten vom DAL, hier habe ich auch noch andere Logik wie z.B. ob der User Berechtigt ist sich Daten zu holen)
  3. Data Access Layer (Holt sich die Daten aus der DatenBank, in mein Fall SQL, wandelt in Objekte um und gibt an den BAL weiter)

Ich habe bisher eine DataObjects.dll wo meine Objekte abgebildet sind, die DataObjects ist derzeit allen anderen bekannt.

Der Server und der ServiceAccessLayer sowie Services kennen eine Interfaces.dll

Und so sollte man das nicht machen?

Die Idee ist: die darüber liegende Schicht / Anwendung interessiert nicht, wo die Daten liegen.

Die Idee verfolge ich ja, oder versuche es 😃
Mein BAL kennt den DAL, und der DAL liefert Ihm ein Objekt zurück. Mein Derzeitiger DAL macht eine SQL / Storage Procedure Abfrage, und wandelt den DataTable in eins meiner Objekte um.

Wenn ich nun das EF implementieren möchte, müsste ich doch eigentlich nur hier, also ab dem BAL eingreifen ?

Also statt :


namespace Zyan.elTorito.BusinessLogic
{
    public class CountryManager
    {
        public static Country[] GetAllCountries()
        {
          return CountryDAL.GetAllCountries();
        }
    }

müsste folgendes möglich sein ?


namespace Zyan.elTorito.BusinessLogic
{
    public class CountryManager
    {
        public static Country[] GetAllCountries()
        {
          return CountryEntityFrameWork.GetAllCountries();
        }
    }

Und wenn man das Repository Pattern berücksichtigen möchte, müsste es so sein?


namespace Zyan.elTorito.BusinessLogic
{
    public class CountryManager
    {
        public static Country[] GetAllCountries()
        {
          return Repository.GetAllCountries();
        }
    }

Und Unterhalb von Repositories würde dann das EF kommen?

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Hi,

ich nochmal 😁 ... so ganz verstanden habe ich es immer noch nicht X(

Habe deswegen nun meine Anwendung mal beiseite gelegt und versucht ein neues "klares" Beispiel zu erstellen in der Hoffnung dass wenn die Komplexität der Anwendung "ausgeblendet" ist es mir etwas klarer wird.

ich habe angefangen mit einer Library "Models" dort befinden sich einfache POCO's :


    public class Country
    {
        [Key]
        public Guid Id { get; set; }
        public string CountryName { get; set; }
        public string CountryCode { get; set; }
    }

Die Models werden in alles andere Verwiesen. Der Aufbau ist derzeit wie folgt:

  1. ConsoleApp
  2. Servicess Access Layer
  3. Zyan Server
  4. Business Layer
  5. Data Layer

Bisher hatte ich meine SQL DB über den SQL Manager gestaltet, dass versuche ich nun über die POCO's in der Model Library und Code First.

In der Console App:


 ServiceAccess SA = new ServiceAccess();
Country[] cl = SA.GetCountries();

Den Service Access benötige ich um die Datenquelle auf Client Seite auszuwählen, in diesem Fall den Zyan Server, könnte aber genauso gut ein WebService oder ein anderes Medium sein welches ein Services zur Verfügung stellt.

Der Zyan Server stellt den CountryService zur Verfügung:


    public class CountryService : ICountryService
    {

        public Country[] GetAllCountries()
        {
            return CountryManager.GetAllCountries();
        }
    }

CountryManager liegt im Business Layer (DLL)


 public class CountryManager
    {
        public static Country[] GetAllCountries()
        {
               IUnitOfWork uow = new UnitOfWork();
              
               //return uow.Countries.GetCountryNamesWithCode("DEU").ToArray();
               return uow.Countries.GetAll().ToArry();
        }
   }

Im DataLayer befinden sich die Repositories, Unit Of Work, Context, Configuration, Datenbank Connection....

Dort habe ich ein Repository Countries:


    public class RepoCountry : Repository<Country>, IRepoCountry
    {

        public RepoCountry(DbContext context)
            : base(context)
        {

        }

        //new customize method for countries
        //gets countries with specific code
        public IQueryable<Country> GetCountryNamesWithCode(string countryCode)
        {
            return GetAll().Where(p => p.CountryCode == countryCode.ToLower());
        }
    }

    public interface IRepoCountry : IRepository<Country>
    {

        //new customize method for countries
        //gets countries with specific code
        IQueryable<Country> GetCountryNamesWithCode(string countryCode);
    }

Interface UnitOfWork:


    public interface IUnitOfWork
    {
        // Save pending changes to the data store.
        void Commit();

        // Repositories
        IRepository<Skill> Skills { get; }
        IRepoCountry Countries { get; }
    }

UnitOfWork:


public class UnitOfWork : IUnitOfWork, IDisposable
    {
        private PKADBContext DbContext { get; set; }
        
public UnitOfWork()
        {
            CreateDbContext();


        }

        //repositories
        #region Repositries
        private IRepository<Skill> _skills;
        private IRepoCountry _countries;

        //get Country repo
        public IRepoCountry Countries
        {
            get
            {
                if (_countries == null)
                {
                    _countries = new RepoCountry(DbContext);

                }
                return _countries;

            }
        }

}

Ich denke sollte Simple genug sein um weiter vorzugehen....

So... Jetzt habe ich ja meinen Business Layer von wo aus ich das Unit of Work anspreche um mir meine Daten zu holen.

Im BusinessLayer wird derzeit ein UnitOf Work initalisiert, im Falle von hinzufügen von Daten werden diese Committet, das heißt, ich kann dort eine Prüfung durchführen, und wenn die Daten irgendwie "korrupt" sind, kann ich ein Rollback ausführen in dem ich nicht Commite?

Ich kann also ein UnitOFWork initialisieren, und dort dann diverse Sachen behandeln, z.B. Land einfügen, Person einfügen, etc... und es wird dann alles zusammen in ein Rutsch an die Datenbank übertragen?

Der DBContext, der in der UnitOfWork dem repository "übergeben" wird, repräsentiert quasi den Zusammenhalt von UnitOfWork und den Repositories?

Dann habe ich 2 Repositories:


public IRepository<Certification> Certifications
{
}

public IRepoCountry Countries
{

}

Der Unterschied ist dass IRepository<Certification> , ein "einfaches" Repository ist, und IRepoCountry ein benutzerdefiniertes Repository ist? Das benutzerdefinierte Repository brauche ich z.B. wenn ich Benutzerdefinierte Abfragen erstelle, wie z.B. Get Countrys by Code ? Während ich für einfaches CreateReadUpdateDelete immer von IRepository ableiten kann ?

Bin ich auf den richtigen weg ?

Danke

16.842 Beiträge seit 2008
vor 9 Jahren

Ich würde das anders machen.

IDBContext ist die Datenbankverbindung.
IUnitOfWork repräsentiert Dein Context
IRepository das spezifische Repository.

Warum hier 3 Elemente:
Wenn Du besonderheiten implementieren willst, zB Caching, dann gehört das meine Ansicht nach in den UoW Container; weder ins Repository noch in den Context.
Zudem - meine Meinung - muss ein UoW-Container nicht wissen, welche Repositories es gibt, geschweige denn die Verbindung. Wieso also manche Beispiele die Repositories in die Klasse des Containers tun ist für mich ein riesen Fragezeichen..

Sowas wie CreateDBContext im Konstruktor... wie willst Du sowas sauber Testen?

PS: POCOs haben keine Attribute wie Key.
Das deckst Du im EF und Code First mittlerweile über das Modelling ab.

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Hallo Abt,

danke für deine Antwort.

Ich würde das anders machen.

IDBContext ist die Datenbankverbindung.
IUnitOfWork repräsentiert Dein Context
IRepository das spezifische Repository.

Mit Containern meinst du jeweils eine DLL ?

Wieso also manche Beispiele die Repositories in die Klasse des Containers tun ist für mich ein riesen Fragezeichen..

Sowas wie CreateDBContext im Konstruktor... wie willst Du sowas sauber Testen?

An testen habe ich noch nicht gedacht, das habe ich so aus einem der zahleichen Beispiele übernommen...


protected void CreateDbContext()
{
DbContext = new PKADBContext();
DbContext.Configuration.ProxyCreationEnabled = false;
DbContext.Configuration.LazyLoadingEnabled = false;
DbContext.Configuration.ValidateOnSaveEnabled = false;

PS: POCOs haben keine Attribute wie Key.
Das deckst Du im EF und Code First mittlerweile über das Modelling ab.

Über das Mapping?


 public class CountryMap : EntityTypeConfiguration<Country>
    {
        public CountryMap()
        {
            // Primary Key
            this.HasKey(t => t.ID);
           .... .... ...
         }
 }

Ist nicht einfach zu verstehen das ganze. Gibt sooo viele Beispiele, und jeder erklärt es anders, und zwischendurch stolpert man immer wieder darüber dass es eigentlich gar kein Sinn ergibt das Muster so anzuwenden... Harte Nuss :evil:

16.842 Beiträge seit 2008
vor 9 Jahren

Mit Containern meinst du jeweils eine DLL ?

Nein, das spielt auch keine Rolle. Das UoW-Element ist ein Container.
Völlig wurst ob 3 DLLs oder eine, solange die Schicht eingehalten wird.

An testen habe ich noch nicht gedacht, das habe ich so aus einem der zahleichen Beispiele übernommen...

Das ist aber ein Grundpunkt für Dependency Injection.

Über das Mapping? Entity Mappings

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Hallo Abt,

danke für deine Geduld...

Ich versuch nochmal ein paar Schritte zurück ...

Der Context ist meine Datenbank ?
UnitOfWork repräsentiert mein Context, also die Datenbank.

Wenn ich UnitOfWork dann wie folgt hätte:


    public class UnitOfWork : IUnitOfWork
    {
        public DbContext Context { get; set; }

        public UnitOfWork()
        {
            Context = new DatenModellVonDB(); // Mein Context, Entities
        }
....

Müsste ich dem Repository dann den UnitOfWork mitgeben?


    public class RepoCountry: BasisRepository<Contact>
    {
        
        public RepoCountry(IUnitOfWork unit):base(unit)
        {

        }

    }


        public BasisRepository(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
            this.dbSet = _unitOfWork.Db.Set<T>();
        }

Dann hätte ich im BusinessLayer Zugriff wie folgt:


            uow = new UnitOfWork();
            Country[] c = new RepoCountry(uow).GetCountryNamesWithCode("DE").ToArray();

Wahrscheinlich jetzt auch doof an der Stelle ein Neues Repository zu erstellen... aber ich versuche mich zu sortieren, wo was hingehört, ich vermute da noch immer Knoten im Gedanken.

Aber so ist dem Unit of Work nicht bekannt welche Repositories es gibt. 😁 Oder?

Ok. Hab Kopfschmerzen. Nochmal drüber schlafen ...

16.842 Beiträge seit 2008
vor 9 Jahren

 public class UnitOfWork : IUnitOfWork
    {
        public IDBContext Context { get; set; }

        public UnitOfWork(IDBContext dbContext)
        {
            Context = dbContext;
        }
}

public class KundenRepository : Repository<Runde>, IKundenRepository
{
        public UnitOfWork(IUnitOfWork uow)
        {
            UoW= uow;
        }

}


Wieso so kompliziert:
Es ist mit dieser Variante vieles viel einfacher zu realisieren - zB Caching.
der UoW Container wird in 5 Repositories injeziert; zwei andere Repository bekommen aber einen ANDEREN UoW-Container (in Summe = 2).
Das ist an vielen Stellen nützlich, wenn man mal mit Caching arbeiten will und mal nicht. Viel einfacher zu kapseln und zu abstahieren als wenn man das direkt im Repository machen würde.

Der IDbContext ist nun aber dafür da, dass Du die DB-Engine sehr schnell tauschen kannst, zB von EF auf MongoDB.

Ich weiß, dass das viele Beispiele anders machen - aber die meisten sind auch fern von jeglicher produktiven Anwendung. Du kannst es auch gerne nur mit 2 Elementen machen, wenn Dir das reicht (UnitOfWork und Repository).

PS: sowas wie ToArray und ToList haben nichts im DAL verloren, sobald der Context IQueryable unterstützt. Das tut EF.
Sowas gehört wenn dann in den BL. ToList erfordert das sofortige Ausführen des Queries. Dieses Wissen, ob das nötig ist, hat aber nur der BL. Ergo ein Repository gibt immer IQueryable zurück.
Du kannst Dir da, wenn Du willst, abschauen, was ich gemacht habe (https://github.com/SchwabenCode/MongoDBRepository). Ich habe hier die Aufteilung in IDBContext und IUnitOfWork nicht gemacht, da dies gezielt nur für die MongoDB ist mit einigen Spezialsachen, sodass das eh nie problemlos tauschbar sein wird.

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Hallo Abt,

hab mir mal dein MongoRepository angeschaut, und habe mal versucht die Trennung von

IDBContext
IUnitOfWork
IRepository

vorzunehmen...

Aber ich hab da noch immer Knoten, 🤔, ja, ich weiß ist schwer zu verstehen, vor allem wenn auch noch Grundlagen fehlen, ich versuch trotzdem irgendwie ein Muster zu finden auf dem ich aufbauen kann, damit ich später, wenn es komplexer wird, einfacher Lösungen implementieren kann, ich denke mal wenn einmal die Basis steht, und funktioniert, kann man auch eher verstehen was und warum und wie... Ist natürlich nur ein Gedanke ...

Also....

Im Business Layer:


public class CountryBL
{
   public void TesteWas(){
            IUnitOfWork IUOW = new UnitOfWork(new MeinDBContext());
            UserRepository userRepository = new UserRepository(IUOW);
            CountryRepository countryRepository = new CountryRepository(IUOW);

            IList<User> userFull = userRepository.GetAllUsersInTable().ToList();           
            IList<Country> countryFullList = countryRepository.GetAllCountriesInTable().ToList();

            var user = new User {ID=Guid.NewGuid(),  FirstNames= "Test Person", LastName= "last name " };
            personRepository.Add(user);

            IUOW.Commit();
   }
}

Unit Of WORK


    public interface IUnitOfWork
    {
     IDBContext Context { get; set; }  // unitof work holding a context
        void Commit();
    }

    public class UnitOfWork : IUnitOfWork
    {
        public IDBContext Context { get; set; } // unitof work holding a context

        public UnitOfWork(IDBContext dbContext)
        {
            Context = dbContext;
        }

        public void Commit()
        {
            Context.SaveChanges();
        }
    }

IDBContext


public interface IDBContext 
{
        DbSet<TEntity> Set<TEntity>() where TEntity : class;
        int  SaveChanges();
        void Dispose();
        DbEntityEntry Entry(object entity);
}

DBContext


    public class DBContext : PKADBContext,IDBContext
    {
        public DBContext(PKADBContext dbContext)
        {
            Context = dbContext;
        }
        public PKADBContext Context { get; private set; }
    }

User Repository


        public class UserRepository : Repository<User>, IUserRepository
        {
             public UserRepository(IUnitOfWork unitOfWork): base(unitOfWork)
            {}
            public IQueryable<User> GetAllUsersInTable()
           {
              return this.GetAll();
           }
        }

        public interface IUserRepository : IRepository<User>
        {
           IQueryable<User> GetAllUsersInTable(); 
        }

Basis Repository


public class Repository<TEntity> : IRepository<TEntity> where TEntity : class

    {
        private readonly IUnitOfWork _unitOfWork;

        public Repository()
        {
        }

        protected Repository(IUnitOfWork unitOfWork) // Each repository holds a unitof work
        {
            _unitOfWork = unitOfWork;
        }
        public virtual IQueryable<TEntity> GetAll()
        {
            return _unitOfWork.Context.Set<TEntity>();
        }

        public virtual void Delete(TEntity entity)
        {
            _unitOfWork.Context.Set<TEntity>().Remove(entity);
        }

        public virtual void Delete(int id)
        {
            var entity = GetById(id);
            if (entity == null) return; // not found; assume already deleted.
            Delete(entity);
        }

        public virtual TEntity GetById(int id)
        {
            return _unitOfWork.Context.Set<TEntity>().Find(id);
        }

        public virtual void Update(TEntity entity)
        {
                _unitOfWork.Context.Set<TEntity>().Attach(entity);
        }

        public virtual void Add(TEntity entity)
        {
            _unitOfWork.Context.Set<TEntity>().Add(entity);    
        }
}


public interface IRepository<TEntity> where TEntity : class
{
        IQueryable<TEntity> GetAll();
        TEntity GetById(int id);
        void Add(TEntity entity);
        void Update(TEntity entity);
        void Delete(TEntity entity);
        void Delete(int id);
}

Wäre das eine Struktur auf die man aufbauen kann?

Der IDbContext ist nun aber dafür da, dass Du die DB-Engine sehr schnell tauschen kannst, zB von EF auf MongoDB.

Wenn ich nun also statt der SQL DB eine MongoDB nehmen möchte,
dann müsste ich quasi DBCONtext von MongoDatabase ableiten?

public class MyDBContext : MongoDatabase,IDBContext
{
}

der UoW Container wird in 5 Repositories injeziert; zwei andere Repository bekommen aber einen ANDEREN UoW-Container (in Summe = 2).


IUnitOfWork IUOW1 = new UnitOfWork(new MeinDBContext());
IUnitOfWork IUOW2 = new UnitOfWork(new MeinAndererDBContext());

UserRepository userRepository = new UserRepository(IUOW);
CountryRepository countryRepository = new CountryRepository(IUOW1);


Und dabei könnte z.B. MeinDBContext die DB auf einem SQL Server sein, und MeinAndererDBContext könnte auf ein anderen SQL Server liegen?

Falls ich das Unit Of Work weg lassen möchte (theoritisch), müsste ich dann den Repository den DataContext statt UnitOfWork übergeben? Und würde so vom BL übers Repository auf die DB gehen?

        
public UserRepository(DBContext context):base(context)
        {
        }

Hoffe bin auf den richtigen Weg. Erstmal wieder genug für heute.

16.842 Beiträge seit 2008
vor 9 Jahren
// interfaces
public interface IDBContext
{
	DBContext {get;} // Context für EF
	MongoDatabase {get;} // Context für MongoDB
}
public interface IUnitOfWork
{
	 IDBContext DBContext {get;}
	 
	// hier Datenbank-spezifisches wie Caching implementieren,
	// das _über alle_ Repositories, die diesen Context verwenden, gilt.
}

public interface IRepository
{
	 IUnitOfWork UnitOfWork {get;}
	 
}

// Classes
public class DBContext : IDBContext
{
  // Mach hier alles um die Connection aufzubauen.
}

public class UnitOfWork : IUnitOfWork
{
	public IDBContext DBContext {get; private set;}
	public UnitOfWork(IDBContext dbContext)
	{
		DBContext = dbContext;
	}
}

public class Repository : IRepository
{
	public IUnitOfWork UnitOfWork {get; private set;}
	public UnitOfWork(IUnitOfWork uow)
	{
		UnitOfWork = uow;
	}
}

Wie gesagt; Du kannst das Element für das Caching weg lassen, aber dann würde ich den DBContext direkt im UoW Implementieren, wie man es in meinem GitHub Repository sieht.

Viele machen das nur mit der Trennung auf zwei Ebenen; nicht wie hier gezeigt auf drei.
Ich hab aber die Erfahrung gemacht, dass dadurch vieles einfacher wird, was eben eine gewisse Trennung der Logik erfordert (nicht nur zwei verschiedene DBMS, sondern zB noch ein XML als weitere Source).

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Okay.

ich brauch also quasi nur das Interface zum Context


    public interface IDBContext 
    {
        PKAExampleContext PKAContext { get; }// Context für PKA
        DbContext EFContext {get;} // Context für EF
        //MongoDatabase MongoContext {get;} // Context für MongoDB
}

 public class MyDBContext : IDBContext
    {
 public PKAExampleContext PKAContext { get; private set; }
        public DbContext EFContext { get; private set; }
}

Dann muss ich dem Unit Of Work nur irgendein Context übergeben ?


IUnitOfWork IUOW = new UnitOfWork(new MyDBContext(new PKAExampleContext()));
//oder
IUnitOfWork IUOW = new UnitOfWork(new MyDBContext(new EFContext()));

16.842 Beiträge seit 2008
vor 9 Jahren

Schaust Du Dir den Code eigentlich an, den ich Dir geb? 🤔

IUnitOfWork IUOW = new UnitOfWork(new PKAExampleContext());
IUnitOfWork IUOW = new UnitOfWork(new EFContext());

Hab ich ja nun mehrfach gezeigt.
Von Context in Context war nie die Rede.......

Und zwei Contexte zeitgleich macht kein Sinn.
Wenn dann nur einer von beidem...

Also

public interface IDBContext
{
    MongoDatabase {get;} // Context für MongoDB
}

oder

public interface IDBContext
{
    DBContext {get;} // Context für EF
}

Bisschen mitdenken wäre schon nett 🙂

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Und zwei Contexte zeitgleich macht kein Sinn.
Wenn dann nur einer von beidem...

Ja, sorry. Habe ich falsch dargestellt. Das meinte ich mit dem

//oder

Entweder ein EFContext, oder ein PKAContext oder ein MongoDatabase Context mit rüber geben.

Hätte besser neue

 Abschnitte mit eingefügt :)
16.842 Beiträge seit 2008
vor 9 Jahren

Das Fazit ist nun? Hats geklappt?

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 9 Jahren

Das Fazit ist nun? Hats geklappt?

Ja. 😁 ich denke schon 👍

Ich hab den Aufruf nun in mein "BusinessLayer"(CountryManager) eingebaut:


public static class CountryManager
{
        private static IUnitOfWork IUOW = new UnitOfWork(new PKADBContext());
        private static CountryRepository countryRepository = new CountryRepository(IUOW);
        private static UserRepository personRepository = new UserRepository(IUOW);

        public static Country CreateCountry(Country country)
        {
            countryRepository.Add(country);
            return country;
        }

        public static IList<Country> GetAllCountries()
        {
            IList<Country> countryFullList = countryRepository.GetAllCountriesInTable().ToList();
            return countryFullList;
        }

        public static User CreatePerson(User user)
        {
          personRepository.Add(user);
        }

        public static void Commit()
        {
            IUOW.Commit();
        }

}

In meinem Country Service:


public class CountryService : ICountryService){
   public void AddCountry(country,person)
   {
     CountryManager.CreateCountry(country);
     CountryManager.CreatePerson(person);
     
     CountryManager.Commit();
   }
}

Ich krieg den Dreh schon noch raus ... 🙂

P
1.090 Beiträge seit 2011
vor 9 Jahren

Nimm die Statische Deklaration der Membervariablen weg, sobalt du mehr als einen Thread hast fliegt dir die ganze Sache um die Ohren.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

2.207 Beiträge seit 2011
vor 9 Jahren

Du könntest statt "static" in dem Fall "readonly" nehmen. Vielleicht meintest du das.

Gruss

Coffeebean