Laden...
H
haarrrgh myCSharp.de - Member
Softwareentwickler Raum Köln Dabei seit 03.06.2008 208 Beiträge
Benutzerbeschreibung

Forenbeiträge von haarrrgh Ingesamt 208 Beiträge

06.12.2011 - 21:31 Uhr

Falls Connectionstring, TCP/IP usw. alles stimmt und auch tatsächlich keine Fehlermeldung kommt, dann mal eine ganz blöde Frage:

UPDATE Patienten SET Name = 'Fritz' WHERE Name = 'Jannik'

Gibt es in der Tabelle 'Patienten' auch tatsächlich einen Datensatz mit Name = 'Jannik'...?

20.05.2011 - 00:38 Uhr

Das ist ein Punkt, den ich bislang zwar schon mehrfach gelesen, aber nicht wirklich verstanden habe: Wozu dient denn überhaupt das zentrale Repository? Vielleicht um die Unterschiede des zentralen gegenüber dem lokalen Repository zu kennen - und diese in der Folge dann anderen zur Verfügung zu stellen?

Genau so ist es. Theoretisch geht es auch ohne zentrales Repository. Wenn ich mit 2 anderen Leuten zusammenarbeite und jeder sein eigenes Repository hat, dann kann ich mir auch aus den Repositories der beiden anderen deren Änderungen pullen.
Das ist aber etwas umständlich, denn je mehr Leute es sind, desto mehr Repositories gibt es, bei denen ich prüfen muß ob neue Änderungen drin sind. Außerdem ist es für Build- und Deploymentzwecke (Stichwort Buildserver, z.B.) sehr hilfreich wenn man nur ein zentrales Repository hat, von dem man genau weiß: da sind alle aktuellen Änderungen drin.
Ohne zentrales Repository muß man nämlich erstmal herausfinden, welcher der 3 Entwickler die aktuellste Version des Codes hat.

Woher wissen B und C, welche Änderungen Sie noch nicht an A geliefert haben? Müssen sie sich das z. B. an einem Datum merken, oder "weiß" es das Git? "Pull" würde dann bedeuten, dass A sich die von B und C weitergeleiteten Differenzen(?) in sein lokales(?) Repository holen würde? Macht er sich dabei nichts kaputt?

Pull heißt genau das.
Push wäre, daß B und C ihre Änderungen stattdessen in ein anderes Repository "schieben" würden. Dafür benutzt man normalerweise ein zentrales.
Git & Co sind schlau genug um sich intern zu merken was sie schon alles gepusht/gepullt haben und was nicht. Deshalb werden bei jedem Push/Pull wirklich nur die Daten gesendet/geholt die wirklich neu sind.
Und nein, man macht sich damit nichts kaputt. Jedes Repository hat lokal eine komplette Historie. Wenn man pullt, werden die Änderungen erstmal nur in der Historie gespeichert, sonst nichts.
Wenn man die Änderungen wirklich in den Codedateien in seiner lokalen Version sehen will, muß man sie explizit aktualisieren. Da kann also auf keinen Fall etwas automatisch überschrieben werden.

Dann noch zum "Mergen": Sollten A und B dieselben Dateien bearbeitet haben und es zu Konflikten kommen, dann hat der Mergende die Arbeit mit dem fremden Code? Nicht, dass ich davon "Angst" hätte; ich will 's nur wissen und verstehen.

Jawoll, der Mergende hat die Arbeit.
So schlimm ist das aber nicht, bei Git & Mercurial ist das Mergen wesentlich einfacher als z.B. bei Subversion. Sogar wenn zwei Leute die gleiche Datei bearbeitet haben, sind Git & Mercurial clever genug um das von alleine zu lösen. Es gibt nur einen einzigen Fall in dem der User wirklich etwas manuell machen muß, nämlich wenn beide **die gleiche Zeile **in der gleichen Datei bearbeitet haben.

In meiner besonderen Situation ist es aber eben so, dass nur Entwickler A an seinem Standort auf das "Master-Repository" zugreifen kann bzw. soll.

OK, das war bis jetzt noch nicht so deutlich. Du willst also, daß Entwickler A das Master-Repository verwaltet und B und C ihm nur zuarbeiten und ihm ihre Änderungen schicken?
Falls ja: das hört sich nach dem typischen Open Source-Projekt-Workflow an.
Der sieht, auf Dein Beispiel gemünzt, ungefähr so aus:

Entwickler A hat ein Repository bei Github/Bitbucket/wasauchimmer.
Das ist das Master-Repository. Entwickler A hat Schreibzugriff, B und C haben nur Lesezugriff.

B und C können sich jederzeit eine Kopie vom Master-Repository machen.
Da gibt es 2 Varianten:
"Clone" --> Kopie auf den lokalen Rechner
"Fork" --> sie müssen auch einen Account beim gleichen Hoster haben. Dann haben sie eine Kopie online, und die können sie wiederum auf ihren lokalen Rechner klonen und dort bearbeiten. Wozu das gut ist, kommt gleich noch.

Dann können sie lokal ihre Änderungen machen.
Wenn sie die fertig haben, committen sie sie. Dann sind die Änderungen in ihren lokalen Repositories.
Jetzt müssen die Änderungen irgendwie in das zentrale Master-Repository. Schreibzugriff darauf hat nur A. Also müssen B und C ihre Änderungen irgendwie an A schicken.

Dafür gibt es 2 Möglichkeiten:
Wenn sie das Repository nur lokal geklont haben, können sie aus ihren Änderungen einen Patch erzeugen. Das ist eine Textdatei die die geänderten Stellen mit Änderungskommentar usw. enthält.
Die schicken sie per Mail an A, und der kann sie in sein Repository importieren.
Oder, wenn B und C einen Fork auf Github/Bitbucket haben: dann pushen sie ihre Änderungen einfach dahin und sagen A, daß er sich ihre Änderungen von dort holen soll.
A kann sich die Änderungen direkt aus den Repositories von B und C pullen (Lesezugriff muß er natürlich haben) und angucken, in seinem lokalen Repository committen und dann ins Master-Repository pushen.

Und dann machen B und C wieder einen Pull vom Master-Repository, und beide haben die jetzt aktuelle Version.

Das ist der typische Ablauf bei den meisten Open Source-Projekten.
Nur wenige Leute haben tatsächlich Schreibzugriff, aber jeder kann sich den Code holen, Änderungen machen und diese an einen von den Leuten mit Schreibzugriff schicken.

Dafür mußt Du Dich allerdings mit dem Gedanken anfreunden, Dein Repository in der Cloud zu speichern.
Wenn Du das nicht möchtest, kannst Du den gleichen Workflow auch nachbauen indem Du Daten per Mail/FTP austauschst, es ist halt nur viel umständlicher als Push/Pull.

20.05.2011 - 00:00 Uhr

Ach ja, "die Cloud" scheidet als Speicherort für die Daten aus. Da ist das Vertrauen noch nicht so weit ...

Die beteiligten Entwickler werden Daten nur per E-Mail, FTP oder USB-Stick austauschen können.

Auch wenn herbivore auch schon etwas dazu gesagt hat, ich formuliere es nochmal etwas deutlicher:

"Die Cloud" ist ein Rechenzentrum bei einem externen Anbieter, irgendwo im Internet.
Beim Datenaustausch per E-Mail oder FTP liegt Dein Code zwangsläufig auf dem Server Deines Providers...und der steht auch in einem Rechenzentrum bei einem externen Anbieter, irgendwo im Internet 😁

Was ich damit eigentlich sagen will: wenn Du Deinem Email- oder Webspaceprovider soweit vertraust daß Du Deinen Code per Mail oder FTP austauschst, dann kannst Du auch einem der großen Repository-Hoster wie Bitbucket oder GitHub vertrauen.

Und das Handling ist darüber wirklich wesentlich einfacher als über FTP/Mail.

19.05.2011 - 01:15 Uhr

Hallo,

in einer verteilten Versionsverwaltung gibt es kein "zentrales" Repository in dem Sinne. Alle Repositories enthalten den kompletten Code UND die komplette Historie, und alle sind identisch.

In den meisten Fällen wird man ein Repository auf einen zentralen Server legen und sich mündlich darauf einigen daß das das "zentrale" ist, weil es einfach leichter zu handeln ist als E-Mail/FTP/USB-Stick.

Man kann natürlich auch ohne zentralen Server arbeiten, aber der Austausch über E-Mail/FTP/USB-Stick ist halt etwas umständlicher. Um bei Deinem Beispiel zu bleiben, würde Entwickler A am Anfang 1x das zentrale Repository weitergeben, Entwickler B und C würden ihm dann irgendwann ihre veränderten Versionen zurückschicken, und er würde ihre Änderungen per **Pull **in sein Repository holen und dort ggf. mergen.

Aber mal eine ganz andere Idee: warum suchst Du überhaupt eine Versionsverwaltung ohne Zugriff auf einen gemeinsamen Server?
Ist es zwingende Voraussetzung daß es keinen geben darf, oder hast Du einfach nur keinen zur Verfügung?

Wenn Du nur keinen zur Verfügung hast...es gibt genügend Anbieter bei denen man Repositories hosten kann.
Siehe Online Quellcodeverwaltung für private Projekte?.

Allerdings sind die meisten nur für Open Source-Projekte kostenlos (sprich, die Repositories sind öffentlich).
Wenn Dein Projekt nicht Open Source ist, würde ich Bitbucket empfehlen. Da bekommt man private Repositories für bis zu 5 User kostenlos.
Die benutzen als Versionsverwaltung allerdings nicht Git, sondern Mercurial.

02.05.2011 - 21:20 Uhr

Du könntest einfach ein zweites Buildscript extra für die Verwendung in TeamCity erstellen, das nichts weiter macht als zuerst Dein Script für die Libs und danach Dein eigentliches Buildscript auszuführen.

28.04.2011 - 22:21 Uhr

@haarrrgh: Dein Tipp wars leider auch nicht. Ich hab die DB zwar im Projektverzeichnis, aber dort wo die Files *.sln und *.suo liegen.

Hm...ich finde, das klingt sehr wohl nach meinem Tipp.

Mach erstmal das was FZelle geschrieben hat (vor allem Punkt 2 😉 ).

Wenn Du das gemacht hast und dann genau den Fall hast den ich in meinem letzten Beitrag beschrieben habe (Programm wird ohne Fehlermeldungen/Exceptions ausgeführt, aber die Daten sind nicht in der Datenbank), lies Dir nochmal den Link durch den ich gepostet hatte.

28.04.2011 - 22:15 Uhr

... die Anzahl der Einträge kann und wird vermutlich in die Tausender gehen.

[...]

Habe leider keine Erfahrungswerte was die Zugriffszeit von großen Datenmengen angeht.

Für einen SQL Server (und jede andere halbwegs vernünftige Datenbank) sind Tausende von Datensätzen nichts.
"Einigermaßen große" Datenmengen fangen bei ein paar Millionen Datensätzen an 8)

28.04.2011 - 00:14 Uhr

Von mir noch ein anderer Schuß ins Blaue:
Ich lese nirgendwo etwas davon daß die "fehlgeschlagenen" Inserts und Updates irgendwelche Fehlermeldungen oder Exceptions auslösen.
D.h. es sieht so aus als ob alles funktioniert hat, die Daten sind danach nur nicht in der Datenbank zu finden.
Richtig?

Falls ja, könnte es sein daß das hier Dein Problem ist?
Datenbank wird nicht befüllt

31.03.2011 - 22:49 Uhr

Naja...wenn man es erst einmal verstanden hat ist es wirklich einfach, aber ggf. dauert es ein bißchen **bis **man es wirklich verstanden hat... 😁 Siehe DI/IoC in der Praxis (und korrekter Aufbau der zugehörigen Klassen)

Mich würde aber sehr interessieren wie Du Logging in Verbindung mit IoC benutzt.
Klar, ich kann mir den Logger injecten lassen, aber dann habe ich ja in **jeder **Klasse die irgendwas loggen muß den ILogger im Konstruktor.

18.03.2011 - 13:43 Uhr

Direkte Erfahrung nicht.
Ich weiß aber, daß das unter der Haube für die eigenliche Versionsverwaltung Mercurial benutzt.
Das ganze Paket Kiln kann natürlich noch mehr, aber der Versionsverwaltungs-Teil ist 1:1 Mercurial.

17.03.2011 - 19:49 Uhr

Ich benutze in Tabellen- und Spaltennamen niemals irgendwelche Sonderzeichen, um genau solche Probleme wie Deins zu vermeiden.

Sicher, man kann in fast allen Systemen mit Tricksereien à la "finanzen__(" + char$(XXX) + "uro)" trotzdem mit solchen Tabellennamen arbeiten, aber es ist einfach lästig wenn man dauernd so ein Kauderwelsch hinschreiben muß.

Ich würde die Tabelle umbenennen, wenn ich Du wäre.
(Das ganze Softwarezeugs ist so schon kompliziert genug, man muß es sich nicht selbst noch schwerer machen 😄)

Außerdem habe ich den Verdacht daß Deine Datenbank nicht gut designt ist.
Aus dem Tabellennamen könnte man schließen daß es auch noch weitere identisch aufgebaute Tabellen namens "finanzen_dollar", "finanzen_taler" usw. gibt.
Wenn dem so ist, dann sollten die Daten alle in EINE Tabelle, mit einer zusätzlichen Spalte für die Währung.

15.02.2011 - 21:16 Uhr

Wenn die Anzahl der Tabellen überschaubar ist, könntest Du für jede Tabelle eine View anlegen, in der Du alle Spalten zu einer zusammenfaßt:

create view BroschuerenSuche as
select
  ID, 
  isnull(Name, '') + cast(Preis as varchar(50)) + isnull(Pnr, '') as Suchfeld
from
  tbl_broschueren_broschuere

Und dann einfach auf die View selektieren:

select ID from BroschuerenSuche where Suchfeld like '%ce%' and Suchfeld like '%4235%'

Die Where-Bedingung mußt Du zwar immer noch selbst zusammenbauen, aber sie ist weniger aufwendig als die im Beispiel von Jack30lena, egal wieviele Spalten die Tabelle hat.

Allerdings hat meine Methode den Nachteil daß sie auch "spaltenübergreifende" Ergebnisse findet.
Z.B. den Suchbegriff "cel5" ("cel" von "Excel" und die 5 aus der Spalte danach).

10.02.2011 - 01:43 Uhr

Mit Entity Framework, SQLite und MSSQL Compact kenne ich mich leider nicht genug aus um hier irgendeine qualifizierte Antwort beisteuern zu können, aber zu MSSQL Express kann ich was sagen, insbesondere hierzu:

Jetzt bin ich inzwischen soweit, dass ich für den Database First Approach sogar MSSQL Express akzeptieren würde. Doch da stellt sich mir das selbe Problem in den Weg, wie bei MSSQL Compact Edition: kein brauchbares Management Tool.

MSSQL Express hat sogar ein sehr brauchbares Management-Tool, nämlich das gleiche SQL Server Management Studio das auch bei den "großen" Editionen vom SQL Server dabei ist.

Allerdings gibt es drei verschiedene Versionen vom SQL Server Express mit unterschiedlichem Funktionsumfang, und das Management Studio ist nicht bei allen dabei. Ich vermute mal, Du hast die Version ohne Management Studio erwischt.

Hier ist die Downloadseite bei Microsoft, inkl. Auswahl zwischen den 3 verschiedenen Versionen. Bei der linken fehlt das Management Studio, bei den beiden rechten ist es dabei.

08.02.2011 - 10:02 Uhr

Wenn es pro Person mehrere Bilder geben soll, dann ist es schon richtig, das Ganze in 2 Tabellen aufzuteilen.
Du schreibst auch daß Du irgendwo eine "Primärschlüsselspalte = ID" hast, aber aus Deinem Text geht nicht hervor in welcher Tabelle die ist.

Es sollte eigentlich so aussehen: Deine Personentabelle braucht einen eindeutigen Schlüssel, auf den in der Bildertabelle verwiesen wird.

PersonId (Primärschlüssel)
Vorname
Nachname
BildId (Primärschlüssel)
PersonId (Fremdschlüssel)
Bild
07.02.2011 - 23:19 Uhr

bei
>
kan man ein privates oder öffentliches Repository anlegen. Hier wird Mercurial genutzt. Ich meine gelesen zu haben, dass auch mittlwerweile SVN möglich ist.

Es ist auf jeden Fall ein Mercurial-Repository, aber man kann von außen per Subversion darauf zugreifen (Quelle).

07.02.2011 - 23:16 Uhr

Ansonsten halt die üblichen Verdächtigen, sofern es um Open Source geht:*CodePlex (Mercurial, Subversion, Team Foundation Server) *Google Code (Mercurial, Subversion) *GitHub (Git) Open Source kostenlos, private Repositories gegen Aufpreis

06.02.2011 - 22:22 Uhr

Wenn ich mir die Frage so durchlese, bin ich mir nicht ganz sicher, was genau Dein eigentliches Problem ist.
Geht es darum...

a) daß die Versionsnummer bei jedem Build automatisch erhöht wird?
b) daß die Versionsnummer nur an einer einzigen Stelle in Deiner Solution liegt, so daß Du nur diese eine Stelle ändern mußt und die Änderung sich auf alle 20 Projekte auswirkt?

Falls es b) ist:
Du kannst irgendwo in Deine Solution eine zentrale .cs-Datei einfügen und die in allen Projekten verlinken.
In diese zentrale Datei kannst Du dann beliebig viele der Attribute schreiben die normalerweise in jedem Projekt in der AssemblyInfo.cs stehen würden.
Du mußt die Attribute die Du in die zentrale Datei schreibst bloß einmalig aus den ganzen AssemblyInfo.cs in den Projekten löschen.
Die Arbeit muß man sich einmalig machen, aber dafür braucht man ab sofort nur noch diese eine zentrale Datei ändern und alle Projekte ziehen sich die Daten aus dieer Datei.

Hier ist ein Link wo das alles inkl. genauer Vorgehensweise nochmal beschrieben wird:
Sharing a common AssemblyInfo between projects in a solution

01.02.2011 - 10:03 Uhr

Nein, FZelle meint bestimmt was anderes:

Wir glauben Dir daß Du nur eine Datenbank hast, aber Du hast die bestimmt in Deiner Projektmappe eingebunden und so eingestellt, daß sie beim Kompilieren & Debuggen in den Ausgabeordner kopiert wird.
Richtig?

Wenn ja, dann hast Du nämlich folgendes Verständnisproblem:1.Du startest den Debugger mit F5, und Dein Programm wird kompiliert und zusammen mit der Datenbank in den Ordner bin/debug (Ausgabeordner!) kopiert. 1.Dein Programm läuft in diesem Ordner und fügt den Datensatz ordnungsgemäß ein, aber natürlich in die Kopie der Datenbank, die dort liegt, und nicht in die originale in der Projektmappe. 1.Nachdem das Programm gelaufen ist, schaust Du in die originale DB in Deiner Projektmappe und findest den Datensatz dort nicht - aber der ist ja auch nicht dort eingefügt worden, sondern in die Kopie der DB im Ordner bin/debug!

27.12.2010 - 16:40 Uhr

Jetzt wo Du es geschrieben hast leuchtet es mir ein und ich verstehe es.
Es ist auch nicht so schwer zu verstehen...es muß einem einfach nur mal einer sagen.

Also:


    public class WinFormFactory : IWinFormFactory
    {
        private readonly IContainer container;

        public WinFormFactory(IContainer container)
        {
            this.container = container;
        }

        public T GetForm<T>()
        {
            return (T)container.Resolve<T>();
        }
    }

    public partial class MainForm : Form
    {
        private readonly IWinFormFactory factory;

        public MainForm(IWinFormFactory factory)
        {
            this.factory = factory;
            InitializeComponent();
        }
    }

    static class Program
    {
        static void Main()
        {
            var container = new Container();
            container.Register<MainForm>();

            Application.Run(container.Resolve<MainForm>());
        }

Dann kann ich hinterher irgendwo im MainForm folgendes machen:

        private void Button1_Click(object sender, EventArgs e)
        {
            var form = this.factory.GetForm<PopupForm>();
            form.Show();
        }

So habt ihr das gemeint, oder?

Zusätzliche Frage:
Ich habe in dem Code oben jetzt keine Interfaces für die WinForms benutzt, sondern benutze direkt die Forms selber.

Also nicht:

container.Register<IMainForm, MainForm>();
container.Resolve<IMainForm>();

sondern:

container.Register<MainForm>();
container.Resolve<MainForm>();

Macht es Sinn für die Forms auch Interfaces zu benutzen? Bei anderen Sachen (Repositories usw.) ist der Sinn klar, aber bei Forms habe ich es weggelassen weil ich den Sinn nicht sehe.
Ich möchte ja nicht zur Laufzeit das MainForm austauschen können o.ä., sondern ich benutze den Container nur damit das Form automatisch seine Abhängigkeiten bekommt.

26.11.2010 - 02:36 Uhr

Ich fürchte, ich habe es immer noch nicht verstanden.

Wenn ich die Forms mit container.Resolve<Mainform>() öffne, dann brauche ich doch an JEDER Stelle, wo ich ein Form öffne, einen Service-Locator-Aufruf.

Peter schreibt, es können "ggf. ein paar Service Locator-Aufrufe" sein.
Bei dem Beispiel mit den WinForms klingt mir das in einer größeren Anwendung aber eher nach hunderten Service Locator-Aufrufen.

Und die sind auch noch kreuz und quer über die ganze Anwendung verteilt (vom Startform öffne ich ein Form mit einer Auftragsliste, von dort aus ein neues mit Details zu einem bestimmten Auftrag, von dort ein neues mit Details zu einer einzelnen Position...)

Jetzt könnte ich natürlich den Container irgendwie kapseln damit ich nur noch ein einer einzigen Stelle wirklich direkt den Container benutze.
Aber ob jetzt an 100 Stellen im Code container.Resolve<IForm>() steht, oder GlobaleWinFormFactory.Create<IForm>(), das macht auch keinen großen Unterschied.
Ich habe dadurch eigentlich nur eine fest verdrahtete Abhängigkeit durch eine andere ersetzt.

Das kann doch nicht der ganze Trick sein, oder?

25.11.2010 - 01:38 Uhr

Manchmal hilft auch folgendes:

Temporäre Tabelle erstellen, nur mit einer Spalte für die ID-Werte:

create table #tmp
(
    ID int
)

Alle IDs nach denen gesucht werden soll dort einfügen:

insert into #tmp values (1)
insert into #tmp values (2)
usw.

Und dann bei der eigentlichen Abfrage die Temptabelle auf die richtige joinen:

select tbOrders.Spalte1, tbOrders.Spalte2, ...
from tbOrders
inner join #tmp on tbOrders.ID = #tmp.ID

So werden nur die Datensätze mit den IDs gefunden die in der Temptabelle stehen.
Ob's was für die Performance bringt kann man so pauschal nicht sagen...ist aber einen Versuch wert.

18.11.2010 - 09:19 Uhr

Wir benutzen Mercurial als Versionsverwaltung und TeamCity (die kostenlose Edition) als CI-Server.

Wir haben gar keinen anderen CI-Server ausprobiert - am meisten liest man über TeamCity und CruiseControl.net, und als ich gelesen habe daß CC.net per XML konfiguriert wird und TeamCity eine richtige UI hat, habe ich mich dafür entschlossen TeamCity als erstes auszuprobieren. Und dann bin ich direkt dabei geblieben 😁

14.11.2010 - 14:22 Uhr

Wenn Du Mercurial statt Git benutzt, kriegst Du bei BitBucket ein privates Repository plus 1 GB Platz umsonst.

Ich sehe gerade daß meine Aussage nicht mehr ganz stimmt - in der Zwischenzeit ist BitBucket von Atlassian übernommen worden, und die Tarife haben sich geändert.

Ab sofort gibt es grundsätzlich unbeschränkten Speicherplatz, sowie unbeschränkt viele private UND öffentliche Repositories.
Der einzige Unterschied ist die Anzahl der User die auf **private **Repositories zugreifen kann: bis 5 User sind kostenlos, bei mehr kostet es je nach Userzahl zwischen 10$ und 80$ pro Monat.

http://bitbucket.org/plans

14.11.2010 - 14:14 Uhr

Meintest Du mich?
Nein, ComponentOne hatte ich vorher noch nie gehört - hatte ich mir vor einem halben Jahr demzufolge auch nicht angesehen 😁
Jetzt ist es aber auch nicht mehr wichtig...wir haben ja schon Stimulsoft gekauft.

12.11.2010 - 00:43 Uhr

Hallo Hampy,

wir haben Stimulsoft Reports gekauft.
Hatte ich vorher nie gehört, aber ich hatte mir diverse Reportgeneratoren runtergeladen und ausprobiert und der konnte am meisten (das war zumindest mein Eindruck).
Wir haben ihn jetzt ca. ein halbes Jahr (haben wg. Zeitproblemen aber noch nicht soviel damit gemacht), aber bis jetzt sind wir sehr zufrieden damit.

Dazu muß man aber auch noch sagen was unser Auswahlkriterium war:
Wir haben hier eine sehr umfangreiche Access 2003-Inhousesoftware die wir Schrittchen für Schrittchen nach .net migrieren, deshalb müssen wir die (vielen) vorhandenen Access-Berichte erstmal 1:1 in .net nachbauen.
Der in Access eingebaute Reportgenerator ist sehr mächtig und bietet ziemlich viele Möglichkeiten - WIE mächtig er tatsächlich ist, das ist mir erst so richtig klargeworden als ich einen Reportgenerator für .net gesucht und mir erstmal MS und Crystal angesehen habe - mit denen habe ich nämlich viele Sachen nicht hingekriegt, die in Access problemlos gingen.

Ich zähle einfach mal ein paar auf, die mir aus dem Stegreif einfallen.
Da es ein halbes Jahr her ist habe ich teilweise nicht mehr im Kopf welche dieser Features bei welchem Berichtsgenerator nicht gingen.
In Stimulsoft ging das auf jeden Fall alles problemlos, das ist der springende Punkt 🙂
*das Seitenkopf-/Seitenfußproblem das Du auch hattest *Textfelder mit u.U. SEHR langen Hinweistexten, die sich zur Laufzeit so vergrößern daß der ganze Text reinpaßt UND dabei noch korrekte Seitenumbrüche über mehrere Seiten machen *Endlosberichte mit "endlosen" Grafiken, deren Pfade aus der Berichts-Datenquelle kommen *Controls auf dem Bericht zur Laufzeit per Code verändern (Größe ändern, sichtbar/unsichtbar, verschieben)...auch in Endlosberichten

Hier ist noch ein Link zu einer Liste mit Features von Stimulsoft (insgesamt 5 oder 6 Seiten).

Ach ja, und den Support fand ich auch sehr gut. Die haben ein Forum, und auch als Benutzer der Demoversion haben die mir da immer geholfen wenn ich Fragen hatte.

05.11.2010 - 13:55 Uhr

Hallo Campy,

das gleiche Problem hatten wir mit den Reporting Services auch, und wir haben dafür auch keine Lösung gefunden (war allerdings noch die Version von SQL Server 2005...2008 haben wir gar nicht erst ausprobiert).

Deshalb haben wir uns dafür entschieden, einen anderen Berichtsgenerator zu nutzen.

06.10.2010 - 10:38 Uhr

Im SQL Server Management Studio ein Abfragefenster öffnen und dann oben im Menü:
"Abfrage" --> "Tatsächlichen Ausführungsplan einschließen"
(oder Strg + M drücken)

Dann führst Du die Abfrage aus, und dann ist unten wo das Abfrageergebnis zu sehen ist noch ein Tab namens "Ausführungsplan", siehe Screenshot.

Das Beispiel auf meinem Screenshot ist natürlich ganz simpel weil ich nur aus einer Tabelle selektiere, aber bei einer JOIN-Abfrage siehst Du da alle Tabellen, inkl. Informationen welche Tabelle wieviel % des Aufwands verursacht hat.

"Table Scan" wie auf meinem Screenshot ist z.B. schlecht...das bedeutet daß es keinen Index auf dem entsprechenden Feld gibt und der Server deshalb die ganze Tabelle durchsuchen muß.

16.09.2010 - 01:21 Uhr

Aaaahh...JETZT hat es bei mir "Klick" gemacht.
Vielen Dank an euch alle! 👍

Mir war einfach nicht klar daß die pauschale Aussage "Service Locator sind böse" sich nicht auf den grundsätzlichen Gebrauch von Container.Resolve bezieht, sondern nur darauf, Container.Resolve direkt in allen Klassen zu benutzen anstatt die Abhängigkeiten über den Konstruktor zu übergeben.

Eine Frage habe ich in dem Zusammenhang aber trotzdem noch:

Bei der impliziten Anwendung geht es darum, das du an einem Ort ein container.Resolve<IFoo>() drin hast. Das kannst du dir als Resolution-Root vorstellen, also die Wurzel, wo die Auflösung beginnt.

Gibt es im Normalfall immer nur EINEN Resolution Root, oder viele?

Peter hat als Beispiel den Controller einer ASP.NET MVC-Anwendung genannt, aber mit ASP.NET MVC kenne ich mich nicht wirklich aus. Kommt man da wirklich mit einer einzigen Stelle in der ganzen Anwendung aus wo Container.Resolve benutzt wird?

Meine ersten Gehversuche mit DI/IoC mache ich gerade in Winforms-Anwendungen, und wenn ich da z.B. in mehreren verschiedenen Forms einen AuftragService brauche, wie komme ich an den?

In jedem Form Container.Resolve?

Für die Forms Interfaces schreiben, IAuftragService als Konstruktorparameter übergeben und das Winform an sich per Resolve erzeugen? (aber dann habe ich auch wieder an jeder Stelle wo ich ein Form lade Container.Resolve stehen)

Oder schreibe ich eine AuftragServiceFactory die intern Container.Resolve<IAuftragService> ausführt und benutze die überall?

Oder ist es gar nicht so schlimm wenn ich an X Stellen in der Anwendung direkt Container.Resolve aufrufe, solange es nur in der Infrastruktur und UI passiert und nicht in den eigentlichen Domain-Objekten?

15.09.2010 - 11:57 Uhr

Wenn Du Mercurial statt Git benutzt, kriegst Du bei BitBucket ein privates Repository plus 1 GB Platz umsonst.

http://bitbucket.org/plans

14.09.2010 - 09:25 Uhr

Hm, anscheinend ist mein letzter Beitrag falsch rübergekommen.
Das was Du erklärt hast, also warum man die Sachen vorher registriert und was der Container eigentlich im Hintergrund macht wenn man per Resolve eine Instanz anfordert, das ist mir alles klar.

Mein Problem ist ein anderes.
Peter hatte in seinem letzten Beitrag ja folgendes geschrieben:

Service Locator ist höchstens eine Art Antipattern, wenn du nicht DI nutzt, sondern überall nur Service Locator.

Aber besser überall Service Locator als gar nichts. Jedoch am besten überall DI und nur an den benötigten Stellen (Keine Kontrolle über die Instanziierung) Service Locator nutzen (bspw. in Attributen), dort geht es nicht anders.

In diesem Fall ist das also eine Kombination aus der impliziten und expliziten Anwendung. Und explizit (Service Locator) nur dort, wo es implizit (per DI) nicht mögich ist.
In diesem Fall ist das sicherlich kein Antipattern, sondern normale Vorgehensweise, die einzig mögliche. Ausser einer normalen Instanziierung 😉

Mein Problem ist die Unterscheidung zwischen der impliziten und expliziten Anwendung.
Wie ein die explizite Anwendung (Service Locator / container.Resolve) funktioniert ist mir klar. Aber wie erzeuge ich ein Objekt implizit / per DI?

Container.Resolve kann es nicht sein da ich den Container ja nicht direkt aufrufen darf.
Mit new kann ich das Objekt auch nicht erzeugen, weil ich ja dann direkt eine Instanz des konkreten Typs erzeugen würde.

Aber wie sonst? Du (gfoidl) hast in Deinem Beispiel auch wieder einen Service Locator verwendet. Kannst Du mir ein Beispiel geben wie das Ganze ohne Service Locator aussehen würde?

Vielen Dank!

14.09.2010 - 01:56 Uhr

Aber besser überall Service Locator als gar nichts. Jedoch am besten überall DI und nur an den benötigten Stellen (Keine Kontrolle über die Instanziierung) Service Locator nutzen (bspw. in Attributen), dort geht es nicht anders.

In diesem Fall ist das also eine Kombination aus der impliziten und expliziten Anwendung. Und explizit (Service Locator) nur dort, wo es implizit (per DI) nicht mögich ist.
In diesem Fall ist das sicherlich kein Antipattern, sondern normale Vorgehensweise, die einzig mögliche. Ausser einer normalen Instanziierung 😉.

Hm...ich glaube, ich habe noch gar nicht verstanden wie die implizite Anwendung / "richtige" DI mit einem Container überhaupt funktioniert.

Explizite Anwendung alias Service Locator ist klar - das ist immer wenn man den Container selber aufruft, sprich wenn irgendwo in einer Klasse per Container.Resolve die von ihr benötigte Abhängigkeit selber erzeugt wird.

Implizite Anwendung ist so wie ich es verstanden habe im wesentlichen Konstruktor-Injection. Die Klasse bekommt ihre Abhängigkeit von außen übergeben und hat keine Ahnung wo diese herkommt oder wer sie erzeugt hat.

In meinem Beispielcode aus meinem letzten Beitrag wäre das also dieser Konstruktor:


public class AuftragService
{
    public AuftragService(IAuftragRepository auftragRepo, IContainer container)
    {
        // usw.
    }
}

Zum "Warmwerden" hatte ich die Abhängigkeiten der AuftragService-Klasse erstmal manuell erzeugt und übergeben, also komplett ohne Container.
Das würde dann ungefähr so aussehen:


static void Main()
{
    string artikelNr = "4711";
    AuftragRepository repo = new AuftragRepository();

    // manuelle Injection
    AuftragService service = new AuftragService(repo);

    int auftragsNr = service.CreateAuftrag(artikelNr);
}

Im nächsten Schritt wollte ich die Injection dann mit einem Container machen.

Aber es scheitert schon daran, daß ich nicht verstehe wie die implizite Anwendung mit einem Container überhaupt funktionieren soll.

Daß ich meine Typen erstmal registrieren muß ist klar:


builder.Register<IAuftragRepository, AuftragRepository>();
usw.

Aber was dann?
Erzeuge ich den AuftragService auch schon über den Container? Wenn ja, wie mache ich das ohne container.Resolve zu benutzen?
Oder erzeuge ich den AuftragService mit new? Dann muß ich ihm aber sofort beim Erzeugen ein IAuftragRepository übergeben, und woher bekomme ich das ohne new oder container.Resolve zu benutzen?

Ich habe jetzt die Dokumentationen von mehreren Containern durchforstet, und in allen Codebeispielen die ich gefunden habe wird immer nur container.Resolve benutzt (z.B. hier bei LightCore).

06.09.2010 - 11:15 Uhr

Du solltest den Update-Befehl mit dem Datum nicht durch Stringverkettung zusammensetzen, sondern Parameter benutzen.

Siehe [Artikelserie] Parameter von SQL Befehlen.
Damit erledigt sich Dein Syntaxproblem von alleine.

05.09.2010 - 02:29 Uhr

So, zuerst mal habe ich nochmal ein paar Fragen zu Peter Buchers und VizOnes Ausführungen, bevor VizOne und sein Command Bus mit der feindlichen Übernahme angefangen haben 🙂

Wenn du eine Instanz vom Container evt. gar nicht oder nur lokal benötigst, gibt es auch Möglichkeiten.
Du kannst bei den meisten Containern entweder den Container selber (Meistens IContainer), eine Factory (In der Form von Func<KundenRepository>), oder sogar einer Lazy-Variante (Lazy<KundenRepository) - d.h. wenn einmal benutzt, wird die Instanz wiederverwendet - injizieren.

Damit kannst du dann nur lokal benötigte Instanzen erstellen, oder die Performance steigern.

Wie das ungefähr aussehen könnte, kannst du anhand von LightCore hier sehen:

  • (Im Moment ist die Seite nicht erreichbar, DNS-Probleme 😦).

Hmm...wieder auf mein Beispiel mit den Aufträgen bezogen...hast Du sowas gemeint?


public class AuftragService
{
    private IAuftragRepository auftragRepo;
    private IContainer container;

    public AuftragService(IAuftragRepository auftragRepo, IContainer container)
    {
        this.auftragRepo = auftragRepo;
        this.container = container;
    }

    public int CreateAuftrag(string artikelNr)
    {
        IArtikelRepository artikelRepo = container.Resolve<IArtikelRepository>();      

        IArtikel artikel = artikelRepo.Get(ArtikelNr);
        
        var auftrag = new Auftrag();
        auftrag.ArtikelNr = artikel.ArtikelNr;
        // usw.

        return this.auftragRepo.Save(auftrag);
    }

    public void DeleteAuftrag(int auftragsNr)
    {
        this.auftragRepo.Delete(auftragsNr);
    }
}

Das AuftragRepository wird direkt im Konstruktor injiziert, weil ich das definitiv sowieso in jeder Methode brauche.
Das ArtikelRepository (und das KundenRepository, das ich in diesem Beispiel der Übersichtlichkeit zuliebe weggelassen habe) brauche ich nicht in jeder Methode, also injiziere ich im Konstruktor den Container, mit dem ich mir die anderen Repositories bei Bedarf erzeuge.

Nachdem ich das bis hierhin geschrieben hatte, habe ich auch mal nach dem LightCore-Link gesucht den Du posten wolltest - das war der hier (erster Abschnitt, ähnliches Beispiel wie mein obiges), richtig?

Wenn mein Beispiel das ist was Du meintest, dann habe ich aber noch eine Frage:
Auch wenn ich den Container hier wenigstens nicht direkt anspreche sondern injizieren lasse, ein Service Locator ist das Ganze trotzdem.
Und es gibt ja auch genug Leute die sagen, Service Locator ist ein Anti-Pattern und sollte niemals und wirklich NIEMALS benutzt werden.

--> Ist das auch wieder so eine übertriebene Pauschalisierung wie z.B. auch das hier?

Nach manchen Artikeln die ich gelesen habe hatte ich den Eindruck als sei der Container DAS zentrale Element der Applikation, über das sämtliche benutzten Objekte erzeugt werden, aber das war dann wohl auch ein bißchen übertrieben.

Oder ist das im Fall des Service Locators wirklich berechtigt?
Ich sehe ein daß der Service Locator dem "Don't call us, we call you"-Prinzip widerspricht (das ist immer die Begründung warum der SL ein Anti-Pattern sein soll), aber ist das so schlimm?

Andererseits hat VizOne hier ein Code-Beispiel mit Commands und CommandHandlern gebracht.

Ich hatte irgendwann weiter oben schonmal geschrieben daß es für mich irgendwie komisch wirkt, meinen AuftragService in AuftragLöschService, AuftragNeuService usw. aufzuteilen.
Andererseits ist das ja genau der Hintergedanke hinter VizOnes ErstelleAuftragCommand (auch wenn ich den ganzen Teil mit CommandBus & CommandHandlern erstmal mental ausklammere, das ist mir noch ein bißchen zu hoch).
Und da bräuchte ich dann auch keinen Service Locator mehr, sondern kann den einzelnen Commands alle ihre Abhängigkeiten direkt per Konstruktor-Injection übergeben, ohne daß die Anzahl der Parameter pro Konstruktor explodiert.

Also ist das wirklich die beste Lösung für mein Problem?

Ich tue mich im Moment noch ein bißchen schwer mit der Anzahl an Klassen die dadurch entstehen würden, aber das ist wahrscheinlich normal wenn man von 1000 Zeilen langen Modulen mit x Funktionen (ja, Access/VBA!) kommt... 😄

03.09.2010 - 00:31 Uhr

Dann mach es am Anfang von Deinem Unit Test. Oder erstelle eine weitere DLL, die das macht was ich beschrieben hatte und die VOR den anderen DLLs aufgerufen wird.

An irgendeinem Punkt halt, wo der Code schon im temporären Ordner liegt, aber bevor DLL A und B benutzt werden.

02.09.2010 - 17:34 Uhr

Da eine der kopierten DLLs dynamisch zur Laufzeit eine DLL nachläd, welche aber nicht in das temporäre Verzeichnis kopiert worden ist.

Damit ich jetzt nicht dauernd von "der DLL, die die andere DLL nachlädt" sprechen muß, geben wir den beiden erstmal Namen:DLL A wird vom Testprojekt in den temporären Ordner kopiert. DLL A braucht DLL B**, das ist die, die nicht automatisch mitkopiert wird.

Also - das ist jetzt zwar vielleicht nicht die eleganteste Lösung, aber:

Du könntest in DLL A und vor dem dynamischen Nachladen von DLL B prüfen ob diese im gleichen Ordner vorhanden ist, und wenn nicht, kopierst Du sie vorher.
DLL A würde in diesem Moment ja schon im temporären Ordner liegen, also kennt sie in dem Moment den exakten Ordnernamen wo DLL B hinmuß (nämlich der, in dem sich DLL A selbst befindet).

02.09.2010 - 15:27 Uhr

Man kann im IIS die Reihenfolge der Defaultseiten einstellen.
Also, wenn es index.aspx UND default.aspx UND index.htm gibt, welche davon als Default geladen wird.

Du mußt das also nur so einstellen, daß Deine HTML-Startseite in der Liste vor den aspx-Seiten steht, dann wird sie automatisch geladen. Und dann halt von Deiner Startseite auf die Start-aspx-Seite verlinken...

02.09.2010 - 13:50 Uhr

Für Anfänger mit Versionisierungssystemen auf Windows empfehle ich stark SVN.
Die anderen genannten Systeme sind (evt. nicht zwangssweise) verteilt (distributed) und haben daher schon mal eine höhere Lernkurve.

Hallo Peter,

das ist natürlich Ansichtssache, aber:
Ich habe als Versionierungssystem-Anfänger auch mit Subversion angefangen, aber bevor ich es wirklich einigermaßen vernünftig produktiv eingesetzt habe, bin ich zu Mercurial gewechselt.
Hauptgrund für mich war, daß ich Mercurial einfacher zu handhaben fand (und wie gesagt, ich war auch nur SVN-Anfänger), bei SVN hat mich damals besonders gestört daß man bei häufigem Hin- und Herwechseln von festem PC zu Laptop immer manuell das Archiv hin- und herkopieren muß, damit man auf dem Laptop die Historie hat und Commits machen kann.

Git fand ich vom Gesamteindruck schwerer zu verstehen, aber der einzige nennenswerte Unterschied von SVN zu HG ist auf den ersten Blick eigentlich nur, daß zu den Grundbegriffen noch Pull & Push dazukommen.

Aber wie gesagt, das ist alles subjektiv...

02.09.2010 - 13:09 Uhr

Tortoise SVN + Visual SVN (alternativ AnkhSVN).

Git + Git Extensions

Dann sage ich mal:
Mercurial + TortoiseHG 😁

Welche Versionsverwaltung man letztendlich nimmt, hängt vom persönlichen Geschmack und von der Arbeitsweise ab.
Hier im Forum wird natürlich jeder das empfehlen, was er selbst benutzt.

Die Repositories auf einer zentralen Netzwerkfestplatte speichern sollte man eigentlich mit jeder Versionsverwaltung können.
Für Mercurial kann ich es definitiv bestätigen, das benutze ich nämlich genau so (privat & Büro).

26.08.2010 - 00:06 Uhr

Kein Problem...ich lerne ja auch noch was dabei 😁

Ich bin heute nur mal kurz über Dein Beispiel geflogen, habe aber auf den ersten Blick nicht viel davon verstanden. Ich muß mir das in den nächsten Tagen auch mal in Ruhe angucken...

24.08.2010 - 02:12 Uhr

Hallo Peter,

danke für Deine Antwort!
Ich stelle mit Freude fest daß Du mehr oder weniger genau das geschrieben hast, worauf ich in den letzten 2 Stunden auch selber gekommen bin.
Das ist ein gutes Zeichen...anscheinend bin ich auf dem richtigen Weg 8)

24.08.2010 - 02:05 Uhr

Hallo VizOne,

ich habe alle Deine Antworten gelesen und bin auf folgendes gekommen.
Erstmal der Code, dann meine Gedanken dazu:

public interface IArtikelRepository
{
	IArtikel Get(string artikelNr);
}

public class ArtikelRepository
{
	public Artikel Get(string artikelNr)
	{
		// Artikel per NHibernate laden
	}
}

public interface IAuftragRepository
{
	int Save(IAuftrag auftrag);	
}

public class AuftragRepository
{
	public int Save(IAuftrag auftrag)
	{
		// Auftrag per NHibernate speichern
		// generierte Auftragsnummer zurückgeben
	}
}

public class AuftragService
{
	private IArtikelRepository artikelRepo;
	private IAuftragRepository auftragRepo;

	public AuftragService(IArtikelRepository artikelRepo, IAuftragRepository auftragRepo)
	{
		this.artikelRepo = artikelRepo;
		this.auftragRepo = auftragRepo;
	}
	
	public int CreateAuftrag(string artikelNr)
	{
		IArtikel artikel = this.artikelRepo.Get(ArtikelNr);
		
		var auftrag = new Auftrag();
		auftrag.ArtikelNr = artikel.ArtikelNr;
		// usw.
		
		return auftragRepo.Save(auftrag);
	}
}

a) Da der Auftrag sich nicht selber speichern darf (wir benutzen kein Active Record), habe ich jetzt doch eine "AuftragService"-Klasse gemacht.
Wir haben weder MVC noch eine nachrichtenorientierte Architektur, sondern eine verteilte Anwendung ähnlich Rainbirds Applikationsserver, da paßt ein "AuftragsService" ziemlich gut rein.

b) Ich habe die Factory weggelassen...denn wenn es ok ist, Domainobjekte auch direkt per "new" zu erzeugen, dann brauche ich die Factory eigentlich auch nicht.
Wie gesagt, es geht mir hier nicht um irgendwelche komplexen Erzeugungen neuer Objekte, sondern letzten Endes darum mir von meinem DAL Objekte mit Daten aus der Datenbank zurückgeben zu lassen.
Nach meinem Verständnis - aber ich lasse mich da gerne korrigieren - sind die Repositories also Abstraktion genug. Mit Factories würde ich dem Auftrag statt dem IArtikelRepository eine ArtikelFactory oder IArtikelFactory injizieren, und dann statt this.artikelRepo.Get einfach nur this.artikelFactory.Create aufrufen.
this.artikelFactory.Create würde aber nichts anders machen als this.artikelRepo.Get nochmal zu kapseln, und das erscheint mir hier unnötig.
Oder übersehe ich irgendwas?

(Daß man Domain-Objekte auch ohne DI erzeugen "darf" scheint für euch Experten auf dem Gebiet also klar zu sein - mir als DI-Anfänger war es nicht klar, aber diese Information macht das Verstehen des "Großen Ganzen" um einiges einfacher.
Es muß einem halt nur mal einer sagen 😁 )

c) Ist es ok, daß die Methode CreateAuftrag sich selbst den Artikel erzeugt?
Oder anders formuliert: DI bedeutet nicht unbedingt daß der AuftragService den kompletten fertigen Artikel von außen bekommt...der Artikel kann auch ruhig innerhalb der Klasse erst erzeugt werden solange dazu ein Repository oder eine Factory benutzt werden, die wiederum von außen injiziert werden.
Richtig?

d) Du hast geschrieben daß Du Domainobjekte zwar mit "new" oder per Factory erzeugst, aber gleichzeitig trotzdem noch per Interface abstrahierst.
Demzufolge benutze ich im AuftragService und im IArtikelRepository einen IArtikel, aber das konkrete ArtikelRepository gibt nicht das Interface, sondern direkt den Artikel zurück. So programmiere ich schon überall gegen Interfaces, aber im Repository benutze ich die konkrete Klasse weil intern eh immer ein Artikel geladen wird...von einem Artikel wird es auch immer nur die eine Ausprägung geben (und nicht wie z.B. in dem Ninject-Beispiel ISword --> Gladius UND Katana).

e) Analog Deinem eigenen Vorgehen:

...ein Objekt, dass mit dem DI-Container erzeugt wird, sollte bei der Erzeugung nur die Informationen benötigen, die der Container sich selbst zusammensuchen kann

...hat der Container in meinem Beispiel also nur die eine Aufgabe, dem AuftragService die konkreten Repositories zu injizieren, und sonst gar nichts.
Nach manchen Artikeln die ich gelesen habe hatte ich den Eindruck als sei der Container DAS zentrale Element der Applikation, über das sämtliche benutzten Objekte erzeugt werden, aber das war dann wohl auch ein bißchen übertrieben.

Macht das soweit alles Sinn, was ich mir da überlegt habe?

Nur eine Sache finde ich auch jetzt immer noch komisch, im Prinzip habe ich in meinem Ausgangsbeitrag in Frage 1 schonmal danach gefragt:
Der Konstruktor meines AuftragService hat in diesem Beispiel schon 2 Parameter, in Wirklichkeit wären es eigentlich 3 (ich brauche ja auch noch einen Kunden, also würde er auch noch ein IKundeRepository bekommen).
Wenn der AuftragsService noch mehr Methoden hat brauchen die vielleicht noch andere Repositories, das würde noch ein paar mehr Parameter bedeuten.
Gleichzeitig werden ArtikelRepository und KundeRepository in den anderen Methoden vielleicht gar nicht gebraucht.
Habe ich dann trotzdem nur einen Konstruktor mit allen Parametern und die werden bei jedem neuen AuftragService ALLE injiziert (und daß die Hälfte nicht gebraucht wird, macht nichts, weil die Erzeugung nicht viel kostet)?
Oder ist das ein Zeichen daß die AuftragService-Klasse zuviel macht und besser in mehrere Klassen aufgeteilt werden sollte?

23.08.2010 - 23:54 Uhr

Hallo zusammen,

vielen Dank für die vielen Antworten.

herbivore, ich fange mal mit Deiner Antwort an weil sie am schnellsten zu beantworten ist:

nur mal ganz pauschal der Einwurf: Eigentlich geht es dir doch um Persistenz, oder?
[...]
DI/IoC brauchst du dabei eigentlich nicht, zumindest nicht vordergründig. Begriffe, die in dem Zusammenhang viel wichtiger sind: DAL, Persistenz, O/R-Mapper und ähnliches.

Hm, vielleicht ist meine Intention nicht ganz rübergekommen.
Natürlich geht es (auch) um Persistenz, aber halt...wie soll ich es formulieren...im Gesamtbild betrachtet.

Am besten gebe ich mal ein kurzes Beispiel damit klar wird was ich meine.

Was wir jetzt haben, ist ungefähr sowas:
(wieder angelehnt an mein Beispiel mit Auftrag/Kunde/Artikel im Ausgangsbeitrag)

public class Auftrag
{
    public void NeuerAuftrag (int KundenNr, string ArtikelNr)
    {
        var kundeRepo = new KundeRepository();
        var artikelRepo = new ArtikelRepository();
    
        Kunde kunde = kundeRepo.Get(KundenNr);
        Artikel artikel = artikelRepo.Get(ArtikelNr);
    
        // und jetzt wird ein neuer Auftrag erzeugt, und
        // dabei auf die Properties von kunde und artikel zugegriffen...
    }
}

Sprich, es gibt schon einen DAL in Form dieser Repositories (die intern NHibernate benutzen).
Es geht mir also nicht darum wie man Objekte mit Daten aus der Datenbank füllt, wie Du anscheinend vermutet hast.

Sondern:
Eigentlich bin ich zum Thema DI/IoC gekommen weil ich angefangen habe mich mit Unit Tests zu beschäftigen.
Dabei habe ich schnell gemerkt daß man den obigen Code nicht wirklich gut testen kann, weil (um die Schlagwörter direkt mit einzubauen 😉 ) die Dependencies nicht injected werden, sondern das Objekt sie selber erzeugt.

Damit man diese Auftragsklasse vernünftig testen kann, darf sie also Kunde & Artikel nicht selber erzeugen, sondern diese derzeit fest verdrahteten Abhängigkeiten müssen von außen übergeben werden. Sprich, die Klasse braucht irgendeine Form von Dependency Injection.

Und damit sind wir im Grunde bei meinen Themen aus dem Ausgangsposting:

a) Mir ist klar wie ich Kunde & Artikel aus der Datenbank lade

b) Ich habe zahlreiche Beispiele zum Thema DI/IoC gelesen und (so hoffe ich) verstanden, bei denen aber immer nur "triviale" Objekte per DI injiziert worden sind, also welche die einfach so "aus dem Nichts" neu erzeugt werden konnten.

--> Und bei mir hat es halt noch nicht "Klick" gemacht, wie ich am besten a) und b) verbinde.
Also, wie ich aus einer Klasse wie dem oben gezeigten Auftrag mit Hilfe von DI/IoC die fest verdrahteten Abhängigkeiten entferne, wenn die zu injizierenden Objekte nicht einfach aus dem Nichts erzeugt werden können, sondern erst noch anhand von irgendwelchen Usereingaben aus der Datenbank geladen werden müssen.

Ich hoffe, jetzt ist es klarer rübergekommen.

Und jetzt versuche ich die Antworten von VizOne zu verstehen...

21.08.2010 - 23:37 Uhr

Ich habe inzwischen gefühlte 1000 Artikel zum Thema DI/IoC/Container gelesen (und ja, ich habe auch hier im Forum danach gesucht und das alles gelesen), ich habe verstanden wofür man es braucht und ich bin der Meinung daß ich es sehr gut gebrauchen kann.

Irgendwie hat es bei mir aber noch nicht so richtig "Klick" gemacht, was die Umsetzung in die Praxis betrifft.
Ich versuche mal mein Problem zu schildern:

So ziemlich alle Beispiele die ich gefunden habe sind mit Loggern, MailSendern oder dergleichen.
Also "Hilfsobjekte" die man einfach einmal mit einem parameterlosen Konstruktor erzeugen und dann benutzen kann.
Sowas hier:

public class MyClass
{
    private ILogger logger;

    public MyClass(ILogger logger)
    {
        this.logger = logger;
    }

    public void MachWas()
    {
        this.logger.Log(...);
    }
}

[...]

// ...und dann beim Start der Anwendung:
Bind<ILogger>.To<FileLogger>();

Und bei Loggern etc. habe ich dann auch schon oft Beispiele gesehen wo direkt beim Programmstart per Container.Resolve<ILogger> EINE Logger-Instanz erzeugt wird, und die wird dann während der ganzen Programmlaufzeit benutzt.

In den "fortgeschritteneren" Beispielen wird dann kein parameterloser Konstruktor benutzt wird, sondern die Objekte haben selber Abhängigkeiten.
Das sind dann aber auch wieder nur Objekte die selber einen parameterlosen Konstruktor haben, also die der Container auch einfach so aus dem Nichts erzeugen kann.

Was ich bis jetzt aber noch nicht begriffen habe:

Was mache ich, wenn ich die Objekte nicht schon beim Programmstart erzeugen, sondern sie irgendwann später anhand Benutzereingaben (z.B. Auftragsnummer) aus der Datenbank laden muß?
Sowas wird in den Beispielen nie gezeigt. Aus meiner Sicht ist das das Wichtigste, denn Logger etc. sind ja nur Beiwerk.

Ein schönes Beispiel was mir gerade in den Sinn kommt ist die Erzeugung von Aufträgen und Auftragspositionen.
Der Auftrag enthält einen Artikel und gehört zu einem Kunden (der Einfachheit halber lasse ich die Auftragsposition für dieses Beispiel jetzt mal weg).
ArtikelNr und KundenNr weiß ich vorher, also brauche ich beim Erzeugen eines neuen Auftrags irgendwoher je ein Artikel- und Kunden-Objekt deren Inhalt ich irgendwann aus der Datenbank laden muß.
Also muß ich irgendwo IArtikel und IKunde "injecten", laut dem was ich gelesen habe idealerweise in einen Konstruktor.

Und jetzt geht es los:

1. Wohin überhaupt mit der Methode die den neuen Auftrag erzeugt?

a) In die Auftragsklasse selber? (sprich, es wäre der Konstruktor)
Dann müßten Artikel und Kunde im Konstruktor des Auftrags übergeben werden...und das werden sie dann bei jeder erzeugten Instanz, auch wenn ich dann vielleicht eine andere Methode ausführen will die weder Artikel noch Kunden braucht, oder einfach nur eine Property eines bestehenden Auftrags ändern will.

b) In eine separate Klasse, "AuftragsManager" o.ä.?
Der AuftragsManager hat wahrscheinlich auch noch mehr Methoden die weder Artikel noch Kunden brauchen (z.B. Auftrag löschen), also habe ich wieder das gleiche Problem wie bei Variante a.
(Und separate Klassen à la "AuftragsErzeuger", "AuftragsLöscher" etc. um das zu vermeiden wären übertrieben, oder?)
Außerdem brauche ich im AuftragsManager dann ja auch irgendwoher ein neues (leeres) Auftrag-Objekt.
Erzeuge ich das direkt im AuftragsManager mit "new", oder muß ich das auch injecten?
Vom Gefühl her hätte ich das jetzt mit "new" gemacht (Sachen wie Logger etc. injected man da es theoretisch verschiedene Sorten von Loggern geben kann, aber ein Auftrag ist und bleibt ein Auftrag), aber irgendwo habe ich auch mal gelesen daß es ein schlechtes Zeichen wäre wenn im Code auch nur ein einziges "new" vorkommt.

2. An welcher Stelle lade ich Artikel & Kunde aus der Datenbank? Ich mache es auf jeden Fall via Repository (NHibernate), aber wo genau?

a) Zwischen der UI (wo der User auf den "Neuer Auftrag"-Button geklickt hat) und der Klasse die den Auftrag erzeugt gibt es noch eine weitere Schicht, "AuftragsService" oder sowas.
Diese Klasse bekommt KundenNr und ArtikelNr übergeben, lädt mit Hilfe von IArtikelRepository und IKundeRepository die eigentlichen Artikel- und Kunden-Objekte und übergibt sie dann der Klasse die den Auftrag erzeugt.
Die Repositories bekomme ich also vom Container, aber die Übergabe der geladenen Artikel- und Kunden-Objekte an die auftragserzeugende Klasse mache ich "zu Fuß".

b) Ich übergebe dem Konstruktor der Klasse die den Auftrag erzeugt gar nicht IArtikel und IKunde selber, sondern IArtikelRepository und IKundeRepository. Das eigentliche Laden von Artikel & Kunde passiert also schon in der Klasse selber, aber halt mit Hilfe der injecteten Repositories.
Dann muß ich der Klasse außer den Repositories selber aber auch noch ArtikelNr und KundenNr übergeben, damit die Klasse weiß was sie überhaupt aus den Repositories laden muß.
Also de facto ZWEI Konstruktorparameter pro benötigtem Objekt.

3. Woher genau kriege ich die Repositories?

a) Erzeuge ich die einmalig beim Starten des Programms und halte sie im Speicher (genau wie ich das oben schon mit dem Logger geschrieben hatte: Container.Resolve<IKundeRepository>)?
Dann sind u.U. ziemlich viele Repositories ständig im Speicher, obwohl ich sie vielleicht nur ganz selten brauche.

b) Lasse ich sie mir automatisch vom Container erzeugen, in dem Moment wo ich sie brauche?
Dann werden oft benötigte Repositories immer und immer wieder neu erzeugt.

4. Ich muß den frisch erzeugten Auftrag ja auch noch in der Datenbank speichern, ebenfalls per Repository.
Wenn die Auftragsklasse sich selbst erzeugt (siehe 1a), dann ist die Auftrag dafür verantwortlich sich selbst zu speichern. Das sollte er eigentlich nicht, oder?
Das würde bei Frage 1 eher für Antwort b) sprechen.
Außerdem, egal ob 1a) oder 1b) richtig ist, bedeutet das ja schon wieder einen weiteren Konstruktorparameter, da ich ja auch noch ein AuftragRepository übergeben muß.
Und wenn Auftrag oder AuftragsManager noch ein paar weitere Methoden haben die noch andere Objekte brauchen, dann brauche ich dafür noch mehr Konstruktorparameter. Das artet ziemlich schnell aus, oder?

Ihr seht, irgendwie fehlt mir da noch der rote Faden 🤔

Deshalb würde ich gerne mal von Leuten mit DI/IoC-Erfahrung hören wie sie das machen.
Gibt es irgendwelche Best Practices für sowas?

13.08.2010 - 14:13 Uhr

Jeder halbwegs vernünftige Texteditor hat auch so eine Suchfunktion, z.B. Notepad++.

Zur Windowssuche: Ich weiß daß die das theoretisch auch kann, aber ich hatte vor ein paar Jahren wiederholt Probleme damit (sie hat nichts gefunden, obwohl es definitiv eine Datei gab die den Suchtext enthielt). Seitdem benutze ich für sowas immer den oben genannten Notepad++.

05.08.2010 - 12:49 Uhr

Genau. Denn egal wie suboptimal er die Konvertierung macht, sie wird auf keinen Fall so lange dauern wie der eigentliche DB-Zugriff 😁

05.08.2010 - 12:46 Uhr

Es gibt mehrere verschiedene Versionen vom SQL Server Express. Bei der "kleinsten" sind z.B. keine Reporting Services und kein Management Studio dabei.

Hier ist ein Editionsvergleich für SQL 2005:
http://www.microsoft.com/Sqlserver/2005/en/us/express-down.aspx

..und hier für SQL 2008:
http://www.microsoft.com/express/Database/InstallOptions.aspx

28.07.2010 - 14:10 Uhr

[...] oder gibt es einen Texteditor, der so eine Funktion hat?

Wenn es speziell um Texteditoren mit Diff-Funktion geht: PSPad

28.07.2010 - 01:17 Uhr

Naja, bei 20-25 Clients und 2-3mal am Tag muß es ja nicht direkt ein eigener Server sein.
Reicht nicht auch irgendein x-beliebiger "normaler" Webspace (kein eigener Server) bei dem eine Datenbank dabei ist?
Natürlich kannst Du Dich mit Deinem Programm dann nicht direkt mit der Datenbank verbinden weil die meisten Webhoster den Zugriff von außen geblockt haben...Du müßtest dann mit Deinem Programm auf Webservices o.ä. zugreifen die auf dem zugehörigen Webspace liegen.

Aber bei der Größenordnung reicht das allemal.

09.06.2010 - 23:15 Uhr

Naja, das sind eigentlich 2 getrennte Probleme:

  1. Dein Programm muß erkennen daß die Datenbank auf die es zugreift alt ist und aktualisiert werden muß

  2. wenn die DB aktualisiert werden muß, dann muß es die Aktualisierung durchführen

Nummer 1 ist ziemlich simpel - füge eine neue Tabelle mit einer einzigen Spalte und einem einzigen Datensatz in die Datenbank ein. In dieser Tabelle speicherst Du eine Versionsnummer (am einfachsten die des Programmes).

Dein Programm liest dann bei jedem Start den Wert aus der Datenbank und vergleicht ihn z.B. mit seiner eigenen Versionsnummer.
Und wenn Du z.B. von Programmversion 2.3 zu Version 2.4 ein Feld in einer Tabelle hinzugefügt hast, und das Programm beim Start merkt daß es selbst Version 2.4 ist und auf einer DB mit Version 2.3 läuft, dann muß es dieses Feld automatisch in der Datenbank anlegen.
Das Programm muß also eine Art eingebaute Datenbankhistorie haben.

Nummer 2 wird u.U. komplizierter, je nachdem wie Deine Datenbankänderungen so aussehen.
Solange einfach nur neue leere Felder hinzugefügt werden (und die erstmal leer bleiben) ist das alles kein Problem.
Komplizierter könnte es nur werden, wenn z.B. ein neues Feld bei allen bestehenden Datensätzen auch gleich mit Daten gefüllt werden muß, die sich wiederum ergeben aus den Inhalten von mehreren anderen Feldern usw.
Oder wenn eine Tabelle gelöscht wird und deren Inhalte vorher teilweise in andere Tabellen verschoben werden müssen...

Das muß Dein Programm dann auch alles können, und das für alle möglichen Kombinationen von Programmversion und Datenbankversion.

DAS wird die meiste Arbeit verursachen.

01.06.2010 - 22:12 Uhr

ich dachte die Communication zwischen datenbank und OR-Mapper ist der geringere Flaschenhals!?

Da die Datenbank normalerweise nicht auf dem gleichen Rechner liegt auf dem der OR-Mapper "läuft", geht jeder DB-Zugriff übers Netzwerk.
Und wenn etwas übers Netzwerk geht, dann ist das normalerweise automatisch der Flaschenhals.