Laden...

Alle Inhalte eines ExecuteReader zur Rückgabe in ein Arry/Arry-List schreiben

Erstellt von nullbytes vor 8 Jahren Letzter Beitrag vor 8 Jahren 2.492 Views
Thema geschlossen
N
nullbytes Themenstarter:in
18 Beiträge seit 2014
vor 8 Jahren
Alle Inhalte eines ExecuteReader zur Rückgabe in ein Arry/Arry-List schreiben

Hallo,

ich habe eine Klasse mit einer Methode die mir ein Datentupel mittels ExecuteReader aus einer Datenbank ausließt.

Da diese ausgelesene Daten Größen für weitere Berechnungen sind, möchte ich diese(s) Tupel an entsprechender Ort und Stelle zurückgeben. Der Methodenaufruf sieht also wie folgt aus:


                string sql = string.Format("SELECT * FROM Tabelle WHERE ID='{0}'", erg);
                DBQuery read = new DBQuery();

                string[] reader = read.DB_Read_All(sql)

Bisher habe ich es immer so gehandhabt, dass ich dafür einfach ein Array zurückgeben habe. Das klappt soweit ganz gut, soll aber nicht wirklich "zum guten Stil" gehören.
Ich habe nun etwas recherchiert und gelesen, dass man dafür doch lieber eine List<> nutzen soll. Diese kann man am Ende mit einem .ToArray einfach umwandeln und übergeben. Klingt für meine Zwecke also gut geeignet.
Ich möchte nun also den Inhalt des ExecuteReaders in die List<> füllen und dann übergeben - das bekomme ich aber nicht hin.

Einseits wird die List nur mit dem ersten Elemente meiner DB gefüllt (also der ID) und ein .ToArray() glaubt auch nicht...

Kann mir wer weiterhelfen?

 
public string[] DB_Read_All(string sqlcommand)
        {
            //.
            //.
            //.

            SqlDataReader reader = cmd.ExecuteReader();
            
            List<string> ausgabe = new List<string>();
            
            int i = 0;
            while (reader.Read())
            {
                ausgabe.Add(Convert.ToString(reader[i])); //Macht nicht was ich will
                i++;
            }

            reader.Close();
            con.Close();

            string[] s = new string[100];
            string[] s = ausgabe.ToArray; //Fehler!

            return s;
        }
2.207 Beiträge seit 2011
vor 8 Jahren

Bisher habe ich es immer so gehandhabt, dass ich dafür einfach ein Array zurückgeben habe. Das klappt soweit ganz gut, soll aber nicht wirklich "zum guten Stil" gehören.
Ich habe nun etwas recherchiert und gelesen, dass man dafür doch lieber eine
>
nutzen soll. Diese kann man am Ende mit einem .ToArray einfach umwandeln und übergeben. Klingt für meine Zwecke also gut geeignet.

Ein Array zurückgeben ist "schlechter Stil", deswegen machst du ne Liste intern und gibst immernoch ein Array zurück? Das macht es nicht besser 😉

Es gibt soviele Beispiele zum ExecuteReader

SqlCommand.ExecuteReader-Methode

Auch mal den Debugger nutzen um zu schauen, was dein Programm macht.

[Artikel] Debugger: Wie verwende ich den von Visual Studio?

Du kannst im Reader auch deine Columnspalten angeben statt dem Index. Auch brauchst du das Array vorher nicht initialisieren. Das ist ja grad der Sinn. Lies dir mal List<T>.ToArray-Methode durch.

Wieso machst du dir nicht, ganz OOP-Like ein Objekt und füllst das in die Liste ab? Und gibst eine List<T> mit T = DeinObjekt zurück?

N
nullbytes Themenstarter:in
18 Beiträge seit 2014
vor 8 Jahren

Du kannst im Reader auch deine Columnspalten angeben statt dem Index

Das weiß ich, ich wollte die Methode aber allgemein halten, damit ich auch mal auf andere Tabellen zurückgreifen kann.

Lies dir mal
>
durch.

Habe ich gemacht und natürlich festgestellt, dass wir hier ja von einer Methode reden und ich die () hinter dem Methodenaufruf vergessen habe... Wäre das schonmal geklärt 👅

Wieso machst du dir nicht, ganz OOP-Like ein Objekt und füllst das in die Liste ab? Und gibst eine List<T> mit T = DeinObjekt zurück?

Ganz einfach: Weil ich sowas noch nie gemacht habe. 😉
Wie so oft versuche ich etwas, stoße an Probleme, schaue wie man es lösen könnte, klicke mich durchs Netz und schaue in verschiedene Literatur und bekomme das Problem gelöst oder lande eben hier. So ist auch meine Lösung mit der Rückgabe mit dem Array entstanden - ich wollte einfach mehrere Rückgabe aus einer Methode haben und das geht ja nicht. Auf der Suche nach Lösungen habe ich den Gedanken aufgeschnappt diese Werte einfach in ein Array zu schreiben und dieses dann zurückgeben. Simpel und es funktioniert - der "Stil" interessiert mich da meistens erstmal nicht so sehr, sofern es funktioniert.

Ein anderer Lösungsvorschlag war diese List<>. Von diesem Objekten, wie du sie vorstellst, habe ich auch schon gehört, nur so wie ich mich kenne bekomme ich das eh wieder nicht gebacken. Wenn man sich immer erst in alle möglichen neuen Sachen einarbeiten muss, verliert man auch immer so unglaublich viel Zeit..

Ich glaube mein Problem liegt momentan auch viel mehr im Füllen der Liste<>/Array[]. Klar kann ich jeden Wert des ExecuteReader einzeln in die Variable speichern, aber dann ist die Methode nicht mehr universell einsetzbar. Da ich ja auch nicht weiß wie viele Elemente mein Array erhalten muss, sollte sich dies bei jeder Interation um ein Feld erweitern - List<> macht das angeblich automatisch. Initialisiert ist diese schonmal, jetzt muss nur noch eine Schleife her, welcher mir die Inhalte des ExecuteReader (Welche sind das eigentlich? Strings? Kann ich irgendwie nicht aus der Doku lesen...) in meine List füllt... 🤔

Hättest du vielleicht mal ein Beispiel für deine vorgeschlagene Lösung? =)

463 Beiträge seit 2009
vor 8 Jahren

                string sql = string.Format("SELECT * FROM Tabelle WHERE ID='{0}'", erg);
                DBQuery read = new DBQuery();

                string[] reader = read.DB_Read_All(sql)

Puuh, da stelllt es mir schon die Fußnägel hoch!
1.SELECT * ist ein NoGo 1.ID = '{0}' - Dafür gibt es Parameter

2.207 Beiträge seit 2011
vor 8 Jahren

[Artikelserie] SQL: Parameter von Befehlen

Btw ist der generische Ansatz schön und gut. Aber dann nimm generische Repositories. Eine Methode, die alles macht...Stell dir mal vor du müsstest herausfinden was die Methode nun holt. Ist unmöglich ohne den Query zu lesen und zu verstehen. Machst du dir Methoden, die eindeutig benannt sind, hast dus sofort. Und das ist lesbarer als ein SQL-Query.

Auch mal das hier anschauen: [Artikel] Drei-Schichten-Architektur

N
nullbytes Themenstarter:in
18 Beiträge seit 2014
vor 8 Jahren

1.SELECT * ist ein NoGo 1.ID = '{0}' - Dafür gibt es Parameter

  1. ist bei mir Standard 😁 und 2. habe ich mal noch abgeändert, sofern du das meinst:
string sql = string.Format("SELECT * FROM M_ISO WHERE ID_TRAFO='" + erg + "'");

@Coffeebean

Wie man an meinem Code sehen kann, habe ich glaube noch einiges zu lernen. Mit einem generischen Ansatz oder generischen Repositories kann ich also leider noch nichts anfangen. Deine Kritik kann ich allerdings auch nicht ganz verstehen: ich finde das super easy einfach einen SQL String zu schreiben und diesen zu übergeben und die Antworten dann entsprechend geliefert zu bekommen. Weiß auch nicht wo da eine Verletzung der 3-Schichten-Architektur sein soll? Trenne doch Eingabe (also GUI) von Datenbank (holen von Daten) und Logik (Verarbeitung der Daten aus der DB unabhängig der zurückgelieferten Werte)... oder nicht?

Anyway!

Habe mich mal für mein Problem an einer Lösung versucht:



        public class content 
        {
            public int id { get; set; }
            public string datum { get; set; }
            public double umgebungstemperatur { get; set; }
            public double rel_luftfeuchtigkeit { get; set; }
            public string wetter { get; set; }
        }


//Methode zum Auslesen mehrere DB-Einträge (ExecuteReader())
        public int DB_Read_All(string sqlcommand)
        {
           // DB Kram
           // ...
           // ...
           // ...

            SqlDataReader reader = cmd.ExecuteReader();
            
            List<content> ausgabe = new List<content>();

            while (reader.Read())
            {
                ausgabe.Add(new content()
                {
                    id = reader.GetInt32(reader.GetOrdinal("ID")),
                    datum = reader.GetString(reader.GetOrdinal("DATUM")),
                    umgebungstemperatur = reader.GetDouble(reader.GetOrdinal("UMGEBUNGS_TEMP")),
                    rel_luftfeuchtigkeit = reader.GetDouble(reader.GetOrdinal("REL_LUFTFEUCHTIGKEIT")),
                    wetter = reader.GetString(reader.GetOrdinal("WETTER"))
                });
            }

            reader.Close();
            con.Close();

            return 0;
        }

So, nun kann ich normal aus meinem Hauptprogramm die Variablen von der Klasse content abgreifen.

Klappt bestimmt, gefällt mir aber nur bedingt. Ich habe jetzt das Problem, dass ich eine Klasse habe wo ich nur diesen einen SQL String auslesen lassen kann - was ist wenn ich bspw. eine andere Tabelle abfrage mit anderen Spalten? Dann muss ich ja meine Liste wieder an diese spezielle Situation anpassen und ein extra Objekt anlegen. Klingt für mich anch viel Code für nichts.

Geht das einfacher bzw. wie kann man das verfeinern? 🤔

Edit: Nee, klappt doch nicht. Alle Werte bleiben 0. Ach ma ey...

2.207 Beiträge seit 2011
vor 8 Jahren

Du gibst in dem Code, den wir da sehen, deine Liste gar nicht zurück....

5.657 Beiträge seit 2006
vor 8 Jahren

Hi nullbytes,

  1. ist bei mir Standard 😄 und 2. habe ich mal noch abgeändert, sofern du das meinst:
string sql = string.Format("SELECT * FROM M_ISO WHERE ID_TRAFO='" + erg + "'");  

Jetzt ist es sogar noch schlimmer 😃

Klappt bestimmt, gefällt mir aber nur bedingt. Ich habe jetzt das Problem, dass ich eine Klasse habe wo ich nur diesen einen SQL String auslesen lassen kann - was ist wenn ich bspw. eine andere Tabelle abfrage mit anderen Spalten? Dann muss ich ja meine Liste wieder an diese spezielle Situation anpassen und ein extra Objekt anlegen. Klingt für mich anch viel Code für nichts.

Geht das einfacher bzw. wie kann man das verfeinern? 👶

Wenn du eine andere Tabelle hast, dann brauchst du doch auch andere Daten, oder? Klar mußt du dafür den entsprechenden Code schreiben. Alles in eine Liste mit Strings zu packen ist keine echte Alternative zu einem vernünftigen Softwaredesign. Es gibt aber ORMs oder den Entity Framework, der dir da viel Arbeit abnehmen kann.

Nee, klappt doch nicht. Alle Werte bleiben 0. Ach ma ey...

Du gibst nirgenswo das Ergebnis zurück.

Christian

Weeks of programming can save you hours of planning

189 Beiträge seit 2014
vor 8 Jahren

Hallo nullbytes,

1.) schau dir wirklich nochmal [Artikelserie] SQL: Parameter von Befehlen an.
Besonders "Problem: Wie verhindere ich SQL-Injection" und das direkt angeschlossene Kapitel "Lösungsweg".
Dann weißt du auch, warum es hier den Profis die Fußnägel kräußelt.

2.) zu deiner 2. Lösung: Wenn du nur ein Return hast und dieses "0" zurückgibt, warum solltest du einen anderen Wert als "0" erwarten können? 😉

2.207 Beiträge seit 2011
vor 8 Jahren

ich finde das super easy einfach einen SQL String zu schreiben und diesen zu übergeben und die Antworten dann entsprechend geliefert zu bekommen.

Und dann? Dann bekommst du "irgendwas". Das kann ein User sein, der deine Applikation nutzt, das kann ein Produkt sein, was dein WebStore verkauft, das kann ein Auto sein, was du anbietest auf deiner Homepage etc.

Jetzt bekommst du basierend auf deinem Query irgendwas zurück und hast keine Ahnung was, musst casten oder oder oder.

Verfolge den OOP-Ansatz und mache dir Objekte, fülle diese ab und wenn du schon plain SQL-Queries nehmen willst: Lass es die obere Schicht nicht wissen. Die ruft nur "GibMirAlleAutos()" auf. Dass darunter ein SQL-Query steckt, dass da über einen reader geloopt wird und jedesmal ein neues Auto gemacht wird interessiert die aufrufende Schicht nicht. Der bekommt nur eine List<T>, also eine List<Auto>, um beim Beispiel zu bleiben, zurück.

Der Link auf die [Artikel] Drei-Schichten-Architektur war mehr prophylaktisch um das deutlich zu machen.

Mit einem generischen Ansatz oder generischen Repositories kann ich also leider noch nichts anfangen.

Kann man nachlesen. Unter dem Stichwort findet man viel.

N
nullbytes Themenstarter:in
18 Beiträge seit 2014
vor 8 Jahren

Hallo nullbytes,

1.) schau dir wirklich nochmal
>
an.
Besonders "Problem: Wie verhindere ich SQL-Injection" und das direkt angeschlossene Kapitel "Lösungsweg".
Dann weißt du auch, warum es hier den Profis die Fußnägel kräußelt.

Okay, erledigt. Ich denke das macht Sinn, schon alleine weil die Probleme habe Double-Werte in der DB zu speichern.

Mein Methodenaufruf und mein SQL-Command sieht wie folgt aus:

//Button "Hinzufügen"
        private void btn_AddNew_Add_Click(object sender, RoutedEventArgs e)
        {
            
            if (cmb_Neu_Auswahl.Text == "Mensch")
            {
                Mensch m = new Mensch();

                //Zuweisung der Eingaben zum neuen m
                m.bezeichnung = txt_AddNew.Text;
                if (!String.IsNullOrEmpty(txt_AddNew_m_fabrikationsnummer.Text)) m.fabrikationsnummer = Convert.ToInt32(txt_AddNew_m_fabrikationsnummer.Text);
                                
                string sqlcommand = "INSERT INTO Mensch(Bezeichnung, Fabrikationsnummer)" +
                                    "VALUES (@bezeichnung, @fabrikationsnummer)";
                
                //Schleife zum Hinzufügen der Informationen
                if (!String.IsNullOrEmpty(txt_AddNew.Text) && !String.IsNullOrEmpty(txt_AddNew_m_fabrikationsnummer.Text))
                {
					AddNewMensch addnew_m = new AddNewMensch();
                    int erg = addnew_m.Insert(sqlcommand);

                    if (erg == 1)
                    {
                        MessageBox.Show("Hinzufügen erfolgreich!", "", MessageBoxButton.OK, MessageBoxImage.Information);
					}
                    else
                        MessageBox.Show("Prüfen Sie Ihre Eingaben!", "Fehler!");
                }
                else
                {
                    MessageBox.Show("Fehlende Angaben!", "Fehler!", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }

Und meine Klasse wie folgt:

    class AddNewMensch
    {

        Mensch trafo = new Mensch();

        string path = Environment.CurrentDirectory;
        string db_name = "DB100.MDF";

        public int Insert(string sql)
        {
            //Connection-Objekt zum Verbindungsaufbau zur DB ohne Informationen zur Datenquelle
            SqlConnection con = new SqlConnection();

            //ConnectionString für SqlConnection-Konstruktor mit allen Informationen zur Datenquelle
            con.ConnectionString = (...);

            //Command-Objekt
            SqlCommand cmd = new SqlCommand(sql, con);

            cmd.Parameters.AddWithValue("@bezeichnung", SqlDbType.VarChar).Value = m.bezeichnung;
            cmd.Parameters.AddWithValue("@fabrikationsnummer", SqlDbType.Int).Value = m.fabrikationsnummer;

            //Fehlerbehandlung
            try
            {
                con.Open();

                int erg = cmd.ExecuteNonQuery();

                if (erg > 0)
                {
                    con.Close();
                    return erg;
                }
                else
                {
                    con.Close();
                    return 0;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                con.Close();
                return 0;
            }
        }
    }

Wenn ich jetzt versuche einen Eintrag zu machen, bekomme ich die Fehlermeldung:

Fehlermeldung:
Die parametrisierte Abfrage '@bezeichnung nvarchar(4000), @fabrikationsnummer int)INSERT INTO' erwartet den '@bezeichnung'-Parameter, der nicht bereitgestellt wurde.

Das verstehe ich nicht.

Zum einen ist in meiner Datenbank die Tabelle 'bezeichnung' mit varchar(50) definiert und nicht nvarchar(4000) und zum anderen habe ich das doch wie in dem verlinkten Artikel gemacht, um genau diese Fehlermeldung zu vermeiden...

5.657 Beiträge seit 2006
vor 8 Jahren

Wenn du dir den verlinkten Artikel nocheinmal anschaust, dann erkennst du auch sehr schnell, was du anders gemacht hast. Alles, was du für die Lösung deines Problems benötigst, ist jetzt schon mehrmals hier gepostet worden. Damit der Thread nicht endlos so weitergeht, mach ich ihn hiermit ersteinmal zu.

Bitte beachte, daß das Forum nicht dazu da ist, daß du hier deinen Code postest, und wir dir deine Fehler suchen sollen. [Hinweis] Wie poste ich richtig?, Punkt 1 und 4.

Christian

Weeks of programming can save you hours of planning

Thema geschlossen