Laden...

Forenbeiträge von nullbytes Ingesamt 18 Beiträge

07.10.2015 - 20:22 Uhr

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...

06.10.2015 - 17:05 Uhr

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...

06.10.2015 - 15:32 Uhr

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? =)

06.10.2015 - 14:58 Uhr

Hallo,

ich denke, dass was du willst sollte aufgrund der OOP von C# kein Problem sein.

Du schreibst einfach deine Klasse in einem separaten .CS File, vergibst deine Felder, Eigenschaften und Methoden und rufst diese dann aus dem Quelltext auf. Du kannst dann je nach deinen Modifizierern auf die entsprechenden Inhalte deiner Klasse zurückgreifen.

Wie das alles genau geht, steht u.a. hier!

06.10.2015 - 14:48 Uhr

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;
        }
01.10.2015 - 14:56 Uhr

Hallo Freunde,

auch wenn ich mit diesem Thread viel gelernt habe, dem Umgang mit dem Kompiler und was es mit diesem Objektverweis auf sich hat, möchte ich den Thread nochmal zum Leben erwecken, da ich es immer noch nicht geschafft habe die Funktionalität in eine separate Klasse auszulagern.

Ich möchte nach wie vor das Auslesen samt Füllen eines DataGrids aus einer Datenbank in eine Methode verpacken, sowie das Updaten dieser beiden Elemente.

Wenn ich diesen Code hier ganz normal im Namespace meines Hauptprogrammes unter der Main-Methode schreibe und ausführe, funktioniert alles wie es soll - die Funktionalität des Quelltextes an sich ist also erstmal gegeben:



        SqlDataAdapter da;
        SqlCommandBuilder cmdbl;
        DataTable dt;
        
       //Fenster wird geladen
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                //Connection-Objekt
                SqlConnection conn = new SqlConnection();
                conn.ConnectionString = @"Data Source=.\SQLEXPRESS;" +
                                        @"AttachDbFilename=" + path + @"\" + db_name + @";" +
                                          "Integrated Security=True;" +
                                          "Connect Timeout=30;" +
                                          "User Instance=True";

                //Command-Objekt
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = conn;
                cmd.CommandText = sqlcommand;

                //DataAdapter
                da = new SqlDataAdapter(sqlcommand, conn);

                //DataTable
                dt = new DataTable();
                da.Fill(dt);

                dataGrid1.ItemsSource = dt.DefaultView;

                conn.Close();
            }

            finally
            {

            }

        }

        //Button Update
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                cmdbl = new SqlCommandBuilder(da);
                da.Update(dt);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.StackTrace);
            }
        }

Nehme ich diesen Quelltext jetzt aber 1:1 und verpacke die Methode private void Window_Loaded() und private void Button_Click() separat in einer Methode in einer Klasse und rufe diese nun auf, klappt das Update nicht mehr - und ich weiß nicht warum.

Coffeebean war so nett und hat mir gesagt, ich solle DataAdapter & DataTable im Konstruktor erzeugen... trotzdem klappt das alles nicht so wie ich will.

Ich habe jetzt ewig hin und her probiert, mich durch verschiedene Fehlermeldungen gekämpft (mein Connection-Objekt wirft mir in der Update-Methode immer diverse Fehlermeldungen raus) und habe jetzt einen Stand, der ohne Fehlermeldungen zum Programmstart führt, durchläuft aber trotzdem nicht funktioniert... ich bin mittlerweile so verwirrt, dass ich überhaupt keinen Plan mehr habe was hier in meinem Quelltext überhaupt noch passiert und überhaupt von nöten ist. Gleichzeitig verstehe ich nicht, wieso ich so viel umändern muss, wenn ich den Quelltext einfach nur vom Hauptprogramm in eine Klasse umsiedel - die Regeln in Bezug auf Namensräume, Scopes und Funktionalität bleiben die gleichen...

Könnte mir bitte einer nochmal beim Aufräumen und umsetzen helfen? X(

Meine jetzige Klasse:

namespace Test_DB
{
    internal class DBQuery
    {
       private SqlConnection conn = new SqlConnection();
        private SqlDataAdapter da = new SqlDataAdapter();
        private SqlCommandBuilder cmdbl;
        private DataTable dt = new DataTable();
        
        string path = @"bla";
        string db_name = "bla";
        string sqlcommand = "bla";

        // DataGrid füllen
        public void DB_DataGrid_Fill(DataGrid dataGrid)
        {

            try
            {
                //Connection-Objekt
                //SqlConnection conn = new SqlConnection();
                conn.ConnectionString = @"Data Source=.\SQLEXPRESS;" +
                                        @"AttachDbFilename=" + path + @"\" + db_name + @";" +
                                         "Integrated Security=True;" +
                                         "Connect Timeout=30;" +
                                         "User Instance=True";

                

                //Command-Objekt
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = conn;
                cmd.CommandText = sqlcommand;

                // ----------------------------------

                //DataAdapter da
                da = new SqlDataAdapter(cmd);
                
                //DataTable dt
                dt = new DataTable();

                //Fill
                da.Fill(dt);
                dataGrid.ItemsSource = dt.DefaultView;
                da.Update(dt);

                conn.Close();
            }
            finally
            {
                
            }
        }

        //DataGrid Updaten
        public void DB_DataGrid_Update()
        {
            //Connection-Objekt - steht hier bloß weil sonst der Kompiler meckert...
            //SqlConnection conn = new SqlConnection();
            conn.ConnectionString = @"Data Source=.\SQLEXPRESS;" +
                                    @"AttachDbFilename=" + path + @"\" + db_name + @";" +
                                     "Integrated Security=True;" +
                                     "Connect Timeout=30;" +
                                     "User Instance=True";
                        
            //Command-Objekt
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = conn;
            cmd.CommandText = sqlcommand;

            da = new SqlDataAdapter(cmd); 
            dt = new DataTable();

            try
            {
                if (da != null)
                {
                    cmdbl = new SqlCommandBuilder(da);
                    da.Fill(dt);
                    da.Update(dt);

                    Console.Beep(1000, 750); //nur zum schauen ob Quellcode durchlaufen wird
                }
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message + ex.StackTrace);
            }
        }
    }
}
23.09.2015 - 11:26 Uhr

"da" ist der Klasse bekannt.

Somit kann jede Methode der Klasse auf "da" zugreifen. Greifst du drauf zu, bevor du es zugewiesen hast, knallts.

Okay, verstanden!

Konstruktor:

SqlDataAdapter da = new SqlDataAdapter();  

Lösung sieht also wie folgt aus:

public void DB_Update_DataGrid(DataGrid datagrid_name, SqlDataAdapter da = new SqlDataAdapter(), DataTable dt = new SqlDataTable())
{
...
}

Geht nicht, da der Kompiler mir sagt:

Der Standardparameterwert für "da" muss eine Kompilierzeitkonstante sein

Habe ich ja noch nie gehört, darum fix recherchiert und das Problem wie folgt gelöst:

 public void DB_Update_DataGrid(DataGrid datagrid_name, SqlDataAdapter da = null, DataTable dt = null)
{
               da = new SqlDataAdapter();
                dt = new DataTable();

...
}

Geht auch nicht, weil immer noch der da = new SqlDataAdapter(cmd) fehlt und somit kommt es zu:> Fehlermeldung:

Die SelectCommand-Eigenschaft wurde nicht initialisiert vor dem Aufruf von: 'Fill'."} System.Exception {System.InvalidOperationException}

Hmmm.. bringt es was der Klasse das cmd-Objekt samt Connection und SQL Anweisung der gesamten Klasse zur Verfügung zu stellen?

Dann kannst du, ohne etwas zu übergeben, von einer Methode auf "da" zugreifen, da es ja in der Klasse (global) bekannt ist.

Siehe p!lles Hinweis hier Scopes

23.09.2015 - 10:37 Uhr

Guten Morgen,

das erschließt sich mir alles nicht. 🙁

1.) Wenn ich den SqlDataAdapter da und den DataTable dt im Konstruktor aufrufe, muss ich diesen doch auch mit bei Methodenaufruf übergeben, sprich quasi:


//Methode
public void DB_Update_DataGrid(DataGrid datagrid_name, SqlDataAdapter da, DataTable dt)
{
...
}

//Aufruf
SqlDataAdapter da = new SqlDataAdapter();
DataTable dt = new DataTable();
DBQuery change = new DBQuery();
change.DB_Update_DataGrid(dataGrid_Edit_Auswahl, da, dt);

Das sieht für mich alles nach doppelt- und dreifachen Code aus?

2.) Ich habe das jetzt mal da und dt im Programm initialisiert, bei Methodenaufruf übergeben und durchlaufen lassen. Natürlich bekomme ich die Fehlermeldung> Fehlermeldung:

"$exception {"Die SelectCommand-Eigenschaft wurde nicht initialisiert vor dem Aufruf von: 'Fill'."} System.Exception {System.InvalidOperationException}"

Klingt logisch, da ja bei meinem SqlAdataper die Übergabe des SqlCommands (cmd) samt SQL-Befehl und Connection-Details fehlt, denn cmd setzt sich zusammen aus:


SqlConnection conn = new SqlConnection();
conn.ConnectionString = @"Data Source=.\SQLEXPRESS;" +
                                    @"AttachDbFilename=" + path + @"\" + db_name + @";" +
                                     "Integrated Security=True;" +
                                     "Connect Timeout=30;" +
                                     "User Instance=True";

//Command-Objekt
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = sql;

Theoretisch müsste ich das doch jetzt auch noch mit in die Methode DB_Update_DataGrid einbauen, richtig?
Das führt doch dazu, dass ich die komplett gleiche Methode wie zum Auslesen von den DataGrids habe... ich kann mir nicht vorstellen dass das so sein muss? Unterm Strich möchte ich doch nichts weiter als das bereits ausgelesene und abgeänderte DataTable zurückschreiben - updaten halt. Dafür muss ich den kompletten Quelltext nochmal in die Methode aufnehmen? Kann ich nicht irgendwie auf die vorherige Methode, die zum Auslesen und eintragen des DataGrids zurückgreifen?
Diese kleine Update-Funktion ärgert mich ziemlich 🙁

22.09.2015 - 23:52 Uhr

Hey Abt,

danke für den Link!

Den Beitrag kenne ich als ich damals bei Anmeldung etwas durchs Forum gestöpert habe. Ich habe ihn trotzdem nochmal gelesen und weiß zwar was du mir sagen möchtest, in diesem Fall aber nicht was ich konkrett ändern sollte und wie mir das bei meinem Problem weiterhilft...

In der Tat habe ich in diesem Fall nicht wirklich an die 3-Schichten-Trennung gedacht. Ich versuche zwar oft GUI von Logik und diese wiederum von Datenbankaktivitäten zu trennen, doch speziell hier ist mir das vielleicht nicht gelungen.

Ich mein Ziel war es eigentlich Quelltext zu sparen, indem ich bestimmte, sich ständig wiederholende Aktivitäten einfach auslagere (wie z.b. das füllen eines DataGrids mit Daten oder das Updaten dieser). Da es sich hier um eine Datenbankaktivität handelt, befindet die sich auch in einer Klasse wo sich nur Methoden welche mit Datenbankaktivitäten zu tun hat befinden. Ich habe noch ein paar andere Klassen. Dort findet i.d.R. entweder Logik oder Optionen um Steuerelemente zu manipulieren (z.b. die Inhalte aus Textboxen zu löschen oder Steuerelemente auszugrauen). Ich denke also - korrigiere mich gerne - das ich schon bemüht bin mich an dieses 3-Schichten-Modell zu halten.

Leider stehe ich immer noch bei dem Problem das ich die Methode nicht zum Laufen bekomme. Weiterhin wäre ich über weitere Hinweise was du konkret anders machen würdest dankbar! Vielleicht sind meine Fragen für Euch Profis ziemlich simpel, aber ich bemühe mich und nehmen gerne Lehre an 🙂

22.09.2015 - 19:15 Uhr

Du meinst so?

public void DB_Update_DataGrid(DataGrid datagrid_name, SqlDataAdapter da, DataTable dt)
        {
            //MsgBox - Wirklich aktualisieren?
            MessageBoxResult result = MessageBox.Show("Achtung!\nEs werden Änderungen an der Datenbank vorgenommen!\n\nMöchten Sie fortfahren?", "Datenbankänderung", MessageBoxButton.YesNo, MessageBoxImage.Warning);

            if (result == MessageBoxResult.Yes)
            {
                try
                {
                    cmdbl = new SqlCommandBuilder(da);
                    
                    if (da != null)
                    {
                        da.Fill(dt);
                        datagrid_name.ItemsSource = dt.DefaultView;
                        da.Update(dt);
                        MessageBox.Show("Änderungen erfolgreich", "Erfolgreich", MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + ex.StackTrace + "\n\nEs wurden keine Daten geändert!", "Fehler!", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }

Ja...öööh... wie rufe ich die Methode denn dann auf? Da muss sich neben dem Namen des DataGrids für die Darstellung dies Grids ja auch noch ein DataAdapter und ein DataTable übergeben... macht das Sinn?

Irgendwie komme ich mir so vor als würde ich mehr "raten" was ich hier programmiere anstatt wirklich zu wissen was ich hier mach... hätte nicht gedacht, dass es so schwer ist diese einfache Funktion einfach in eine separate Methode auszulagern... X(

Edit: Dieses if(update ==1) war Unsinn und entstammte aus einer anderen Lösungsversuch. Copy&Past Überbleibsel... Habs entfernt, danke für den Hinweis!

22.09.2015 - 18:45 Uhr

Hallo,

danke erstmal für Eure Hilfe!

Ich habe mich jetzt versucht mit Hilfe des Debuggers und der Fehlermeldung wie in den verlinkten Posts zum Problem vorzuhangeln.
Dafür habe ich mir nach kurzem einlesen die StackTrace-Funktion zu eigen gemacht.

Ich glaube mein Problem auch gefunden zu haben.

In der Methode zum Updaten der Klasse bleibt die Anweisung

da.Update(dt);

laut Debugger null.

Das sollte heißen, dass ich wie im oben verlinkten Post mich um die NullReferenceException kümmern sollte. Dafür habe ich mir das Beispiel zu hilfe gemacht.

Aus

 
                     try
                    {
                        cmdbl = new SqlCommandBuilder(da);
                        da.Update(dt);
                        MessageBox.Show("Änderungen erfolgreich", "Erfolgreich", MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message + "\n\nEs wurden keine Daten geändert!", "Fehler!", MessageBoxButton.OK, MessageBoxImage.Error);
                    }

wurde folgender Versuch:


                     try
                    {
                        cmdbl = new SqlCommandBuilder(da);
                        if (da != null)
                        {
                            da.Update(dt);
                            MessageBox.Show("Änderungen erfolgreich", "Erfolgreich", MessageBoxButton.OK, MessageBoxImage.Information);
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.StackTrace + "\n\nEs wurden keine Daten geändert!", "Fehler!", MessageBoxButton.OK, MessageBoxImage.Error);
                    }

Immerhin - beim Ausführen des Programmes fliegen mir keine Exceptions mehr um die Ohren, ich habe den Fehler also in der Tat gefunden. Meine Datenbank wird aber auch nicht geupdatet... X(

Kann mir wer erklären was ich falsch gemacht habe bzw. wie ich das jetzt ändere? 🤔

Irgendwie muss ich doch jetzt den DataAdapter da neu initialisieren, oder?

22.09.2015 - 11:34 Uhr

verwendetes Datenbanksystem: <MySQL / .mdf>

Hallo Freunde,

ich bin in der Lage bin ein DataGrid mit Daten aus einer DB zu füllen und diese Daten später auf Button-Druck in die DB zurückzuspielen und dort zu speichern (Stichwort: update). Dies klappt wunderbar.

Nun habe ich mir eine Methode in einer Klasse erstellt, mit der ich diese beiden Funktionen auslagern und universeller gestalten möchte und weitergehend den Quelltext im Projekt übersichtlich und sparsam zu halten.

Am Quelltext habe ich nichts geändert.


internal class DBQuery
    {

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

        SqlDataAdapter da;
        SqlCommandBuilder cmdbl;
        DataTable dt;

        //Methode zum Füllen von DataGrids
        public void DB_Fill_DataGrid(string sql, DataGrid datagrid_name)
        {
            //Connection-Objekt
            SqlConnection conn = new SqlConnection();
            conn.ConnectionString = @"Data Source=.\SQLEXPRESS;" +
                                    @"AttachDbFilename=" + path + @"\" + db_name + @";" +
                                     "Integrated Security=True;" +
                                     "Connect Timeout=30;" +
                                     "User Instance=True";

            try
            {
                //Command-Objekt
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = conn;
                cmd.CommandText = sql;

                //DataAdapter
                da = new SqlDataAdapter(cmd);
                dt = new DataTable();

                //DataTable + GridView füllen
                da.Fill(dt);
                datagrid_name.ItemsSource = dt.DefaultView;
                da.Update(dt);

                conn.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                conn.Close();
            }
            finally
            {
                conn.Close();
            }
        }

       //Methode zum Updaten des DataGrids
        public void DB_Update_DataGrid(int update)
        {
            if(update == 1)
            {
                //MsgBox - Wirklich aktualisieren?
                MessageBoxResult result = MessageBox.Show("Achtung!\nEs werden Änderungen an der Datenbank vorgenommen!\n\nMöchten Sie fortfahren?", "Datenbankänderung", MessageBoxButton.YesNo, MessageBoxImage.Warning);

                if (result == MessageBoxResult.Yes)
                {
                    try
                    {
                        cmdbl = new SqlCommandBuilder(da);
                        da.Update(dt);
                        MessageBox.Show("Änderungen erfolgreich", "Erfolgreich", MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message + "\n\nEs wurden keine Daten geändert!", "Fehler!", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                }
            }
        }
        }
    }

Diese beiden Methoden rufe ich jetzt bei zwei verschiedenen Events auf:

        //Modell in CMB wurde ausgewählt
        private void cmb_Edit_Modell_DropDownClosed(object sender, EventArgs e)
        {
            if (cmb_Edit_Modell.SelectedItem != null)
            {

                //SQL-Anweisung
                string sqlcommand = "bla bla bla";

                DBQuery Fill_DataGrid = new DBQuery();
                Fill_DataGrid.DB_Fill_DataGrid(sqlcommand, dataGrid_Edit_Auswahl);
            }
        }

        //Update Button
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            DBQuery update = new DBQuery();
            update.DB_Update_DataGrid(1);
        }

Mein Problem ist jetzt, dass ich beim Drücken auf den Update-Button beim zurückspielen meiner Daten in die Datenbank die Fehlermeldung "> Fehlermeldung:

Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt " bekomme.

Ich glaube auch zu wissen woran das liegt (nämlich das ich mit DBQuery update = new DBQuery() ein neues Objekt instanziere und nicht auf das bereits erstellte zurückgreife - richtig?), jedoch weiß ich nicht wie ich dieses Problem behebn kann...

Hat da jemand eine Idee und kann mir helfen?

16.09.2015 - 20:17 Uhr

Hallo,

danke für diese Antwort!

Hat in der Tat beim zweiten Versuch super geklappt! Ich kannte die Collapsed-Einstellung u.a. aus der Doku, irgendwie habe ich sie nur nicht richtig umgesetzt bekommen, weshalb ich gezweifelt hatte ob diese wirklich die richtige Einstellung für mich ist. Nachdem du mir nochmal bestätigt hast, dass das so richtig ist, habe ich nach etwas basteln alles hinbekommen. Danke dafür!

Kannst du mir vielleicht kurz erklären was es mit der generischen Struktur auf sich hat und wieso das vermutlich die bessere Lösung wäre?

Du setzt die Controls vermutlich auf Visibility.Invisible ? Damit der Platz nicht "verbraucht" wird müsstest du sie auf** Visibility.Collapsed** setzen.

Sinnvoller wäre vermutlich eine generische Struktur, die über Databinding und einem DataTemplateSelector dir pro Gerät(etyp) das passende Template für das selektierte Gerät zur Anzeige gibt.

13.09.2015 - 21:38 Uhr

Hallo Freunde,

ich habe ein Problem bei der Umsetzung einer Idee, bei der ich mir leider nicht zu helfen weiß.

Ich möchte mittels einer Oberfläche, bestehend aus Labels und TextBoxen, gewisse Daten verschiedener Gerätschaften in eine Datenbank schreiben. Jede Gerätschaft enthält spezifische Details und somit unterschiedliche Eingabemasken. Ich möchte nun, dass die Oberfläche je nach Auswahl in der ComboBox ihre Labels und TextBoxen anpasst, damit die spezifischen Daten eingegege werden könnnen, um sie später dann zu speichern.

Die Eingabemasken, bestehend aus Stackpanels, Grids, Labels, Textboxen und Buttons, sind individuelle Anfertigungen und befindet sich in einer Groupbox. Meine Idee war jetzt, einfach je nach Auswahl in der ComboBox die richtige Groupbox mit der entsprechenden Eingabemaske mittels Nenndaten.Visibility = System.Windows.Visibility.Visible; ein- bzw. auszublenden.

Problem ist allerdings, dass die separaten Masken nicht "verschwinden" im Sinne von "sie sind weg", sondern lediglich nicht angezeigt werden. Das heißt, dass der Platz auf der Oberfläche der Form weiterhin reserviert bleibt und lediglich leer ist.

Wie kann ich das ändern bzw. wie kann ich mein Problem beseitigen?

Die Form soll einfach nur die entsprechende Eingabemaske zur ausgewählten Auswahl anzeigen.

Meine Idee war jetzt in der Groupbox ein Grid zu definieren, indem ich die Spalten und Zeilen einfach mit bestimmten Labels und TextBoxen doppelt belege und je nach Auswahl in der Combobox einfach die richtigen Steuerelemente einblende und die anderen ausblende. Ist glaube machbar, jedoch recht unschön, oder? Hat jemand eine bessere Idee?

10.09.2015 - 23:22 Uhr

Hi,

danke für deine Antwort. 👍 Leider war das nicht kanz korrekt.

Ich habe das untere StackPanel entfernt (für ein Grid eigentlich sowie sinnlos...) und das DockPanel im Grid hinterlegt.

<!-- Rechte Anzeigeseite -->
        <Grid DockPanel.Dock="Right" HorizontalAlignment="Right" VerticalAlignment="Stretch" Background="AliceBlue">
            <!-- Spalten Definition -->
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!-- Reihen Definition -->
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

            <!-- Inhalt -->
            <TextBox Name="txt_" Grid.Row="0" Width="250" Height="150" Margin="5,5,5,5"></TextBox>
            <TextBox Name="txt_1" Grid.Row="1" Width="250" VerticalAlignment="Stretch" Margin="5,5,5,5"></TextBox>
        </Grid>

So sieht der fertige und für mich richtige Entwurf aus.
Blöd dass man das nicht auf Anhieb selber hinbekommt oder erst nachdem man einen Thread erstellt hat... 😜

10.09.2015 - 19:04 Uhr

Hallo Freunde,

ich habe ein simples Problem, bekomme es nicht gelöst und bin kurz vorm Durchdrehen.

Eigentlich möchte ich lediglich, dass sich ein Grid samt der beiden darin befindlichen Steuerelemente, in einem DockPanel je nach Fenstergröße über die komplette Höhe rechts im Fenster erstreckt.

Nochmal eine umfassender Erklärung:
Ich habe ein Fenster welches links eine Leiste mit Buttons und rechts ein Grid mit zwei Steuerelemente beinhaltet. In der Mitte möchte ich dann etwas anderes einfügen, darum befinden sich die die beiden Leisten in einem DockPanel.
Die rechte Seite besteht aus einem Stackpanel mit einem Grid. Darin sind zwei TextBoxen enthalten. TextBox1 (die obere von beiden) ist in ihrer Größe fest definiert. TextBox2 soll jetzt einfach die Breite und die Höhe des restlichen Hauptfensters einnehmen (quasi die Spalte des Grids einfach über die komplette Höhe ausfüllen)... und dafür bin ich zu blöd?! So schwer kann das doch nicht sein 😕

Problem ist, dass die TextBox2 ihre Größe konstant beibehält und sich nicht dem Fenster anpasst...

Kann mir vielleicht jemand helfen?!

<Window x:Class="Tool.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TR" ShowInTaskbar="True" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen">

    <!-- DockPanel für die Ansicht von Menü und GroupBoxen -->
    <DockPanel LastChildFill="True">
        
        <!-- Menüleiste ganz oben mit Programmbuttons -->
        <Menu Name="menu_1" Height="25" DockPanel.Dock="Top" IsMainMenu="True">
                <MenuItem Header="Datenbank">
                <MenuItem Header="Verbindung">
                    <MenuItem Header="Verbindung testen" Click="MenuItem_DB_Test_Click"/></MenuItem>
                <MenuItem Header="Hilfe" Click="MenuItem_DB_Help_Click"/>
            </MenuItem>
            <MenuItem Header="Einstellungen">
                <MenuItem Header="Grenzwerte" Click="Limits_Click"></MenuItem>
                <MenuItem Header="Bewertungstabelle"></MenuItem>
            </MenuItem>
            <MenuItem Header="_?" Click="MenuItem_Click_1" />
        </Menu>

        <!-- Linke Button Leiste-->
        <StackPanel DockPanel.Dock="Left">
            
            <Grid>
                <!-- Spalten Definition -->
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions> 
                
                <!-- Reihen Definition -->
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                
                <!-- Inhalt -->
                <Button Name="btn_new0" Grid.Row="0" Width="120" Height="40" Margin="5,5,5,5" Click="btn_new0_Click">Neu</Button>
                <Button Name="btn_new1" Grid.Row="1" Width="120" Height="40" Margin="5,5,5,5">Ändern</Button>
                <Button Name="btn_new2" Grid.Row="2" Width="120" Height="40" Margin="5,5,5,5">Messwerte</Button>
                <Button Name="btn_new3" Grid.Row="3" Width="120" Height="40" Margin="5,5,5,5">Berechnen</Button>
                <Button Name="btn_new4" Grid.Row="4" Width="120" Height="40" Margin="5,5,5,5"></Button>
                <Button Name="btn_new5" Grid.Row="5" Width="120" Height="40" Margin="5,5,5,5">New...</Button>
                <Button Name="btn_Close" Grid.Row="6" Width="120" Height="40" Margin="5,5,5,5" Click="btn_Close_Click">Beenden</Button>
            </Grid>
        </StackPanel>

        <!-- Rechte Anzeigeseite -->
        <StackPanel DockPanel.Dock="Right" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">

            <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <!-- Spalten Definition -->
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>

                <!-- Reihen Definition -->
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <!-- Inhalt -->
                <TextBox Name="TextBox1" Grid.Row="0" Width="250" Height="150" Margin="5,5,5,5"></TextBox>
                <TextBox Name="TextBox2" Grid.Row="1" Width="250" VerticalAlignment="Stretch" Margin="5,5,5,5"></TextBox>
            </Grid>
        </StackPanel>


    </DockPanel>
</Window>

17.12.2014 - 22:04 Uhr

Hallo Freunde,

ich spiel gerade ein wenig mit Datenbanken herum und habe es bereits geschafft eine Verbindung zu einer kleinen DB herzustellen sowie Daten auszulesen.

Um den Quelltext übersichtlich zu halten möchte ich gerne eine Methode erstellen welche quasi für mich den Verbindungsaufruf initialisiert und ich später lediglich noch an der richtigen Stelle die Methode aufrufen muss um die Verbindung öffnen zu können.

Leider klappt dies nicht. Ich glaube, dass hat etwas damit zu tun, dass meine return-Werte "nicht ankommen".

Hier mal mein Quelltext:

Die Methode zum Verbindungsaufbau:


public string InitialConnection()
            {

                //Erzeugt NewLine in MessageBox
                string CrLF = Environment.NewLine;

                SqlConnection con = new SqlConnection();
            
                con.ConnectionString = @"Data Source=.\SQLEXPRESS;" +
                                       @"AttachDbFilename=...;" +
                                        "Integrated Security=True;" +
                                        "Connect Timeout=30;" +
                                        "User Instance=True";
            
                return CrLF + con;
            }

...und hier der Versuch später die Verbindung herzustellen:

 private void btn_con1_Click_1(object sender, RoutedEventArgs e)
        {

            //Fehlerbehandlung beim Öffnen der Verbindung
            try
            {

                InitialConnection();

                con.Open();

                MessageBox.Show("Die Verbindung wurde erfolgreich hergestellt!" + 
                                CrLF + CrLF +
                                "Data Source: " + con.DataSource +
                                CrLF +
                                "Server Version: " + con.ServerVersion);

                con.Close();
            }

Die Fehlermeldung sagt mir, dass der Name "con" und "CrLF" im aktuellen Kontext nicht vorhanden sind. Ich dachte jedoch, dass dies doch eigentlich nach dem Aufruf der InitialConnection() Methode und dem Empfang des return-Werts der Fall sein soll. Der Verbindungsaufbau klappt ohne, wenn ich die Anweisung in meiner Methode komplett in die try-Anweisung kopiere.

Kann mir wer helfen?

09.11.2014 - 22:11 Uhr

Hallo Leute,

ich habe eher eine theoretische Frage zur Umsetzung eines Container-Layouts.
Da ich recht neu im Umgang mit WPF bin, war der Umgang mit den Container-Layouts anfänglich etwas gewöhnungsbedürftigt, mittlerweile finde ich die Idee dahinter allerdings super. Nichtsdestotrotz habe ich teilweise noch Probleme bei der Umsetzung oder Ideenfindung, wie in diesem Fall, wo ich Euch um Rat fragen möchte.

Folgendes Problem:

Ich habe in einem TabItem eine Übersicht erstellt, welche Links drei vertikal ausgerichtete Buttons enthält, in der Mitte eine ListBox und rechts davon soll ein Container-Layout für mehrere Labels erscheinen.

Die ListBox in der Mitte enthält eine Liste mit auswählbarer Items. Gehen wir mal von Autos aus.
Die Buttons links sind lediglich zum Bestätigen der Auswahl der ListBox, zum Abbrechen oder Beenden vorhanden.

Rechts von der ListBox soll nun eine Übersicht erstellt werden, welche mir beim Auswählen eines Autos mir bereits einige vordefinierte Parameter, wie bspw. Hersteller, Farbe und Verbrauch angibt.

Dieser beschriebene Tab enthält als "Grundcontainer" ein DockPanel. Die Buttons sind nun mittels StackPanel in diesem DockPanel integriert. Den Rest der Fläche nimmt nun die Liste ein, welche aber noch Platz für den Container mit den Labels machen muss.

Wie erstelle ich jetzt den Container mit den Label-Elementen? Welchen Container muss ich da nutzen? Wie muss ich diesen in Bezug auf die ListBox erstellen?

Mir ist wichtig, dass die Proportionen der Fenster zueinander bei unterschiedlichen Fenstergrößen bewahrt wird. Mir fällt immer wieder schwer, einzelne Steuerelemente so zu erstellen, dass sie "komplette Flächen" füllen, nicht versetzt zueinander sind oder beim verändern der Größe des Programmfensters verschwinden. Dafür sollten doch - meiner Auffassung nach - die Layout Container da sein.

Auf der Suche nach einer Lösung hat mich dieser Thread auf die Idee eines DataGrids gebracht. Klingt erstmal nicht schlecht, vorallem da ich jedem Label eine eigene "Zelle" samt "Zelle" für die Ausgabe zuordnen könnte und ich mir sicherlich keinen Kopf um die richtige Positionierung der Labels machen müsste.

Problem ist allerdings, dass dieser beim Einfügen meine ListBox schrumpfen lässt, da das DataGrid ja dann das letzte Element wäre, da es rechts von der ListBox stehen soll. Wenn ich feste breiten Verteile, kommt die Symmetrie beim Verändern der Fenstergröße durcheinander. Ideal wäre, wenn der Label-Container eine feste Größe bekommt und sich lediglich die ListBox beim Ändern der Fenstergröße an anpasst.

Habt Ihr eine Idee wie ich vorgehen sollte? Ist das DataGrid dafür geeignet oder wäre ein bswp. Grid besser? Wie muss ich mein DockPanel anpassen? Jemand eine ganz andere Idee?

Hier noch der bisherige XAML Code:

<Window x:Class="myTrafo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="myTrafo" Height="900" Width="1600" MinHeight="800" MinWidth="600" MaxHeight="1080" MaxWidth="1920" DataContext="{Binding}" ResizeMode="CanResize" Name="myTrafo_Main" WindowStyle="SingleBorderWindow">
    
    <!-- DockPanel für die Ansicht von Menü, TabControll und StatusBar -->
    <DockPanel LastChildFill="True">

        <!-- Menüleiste ganz oben mit Programmbuttons -->
        <Menu Name="menu_1" Height="25" DockPanel.Dock="Top" IsMainMenu="True">
            <MenuItem Header="File">
                <MenuItem Header="_Cut" Command="Cut"/>
            </MenuItem>
            <MenuItem Header="_Edit" />
            <MenuItem Header="_View" />
            <MenuItem Header="_Windows" />
            <MenuItem Header="_Help" />
            <MenuItem Header="_?">
                <MenuItem Header="About" />
            </MenuItem>
        </Menu>
        
        <!-- Statusbar am Boden des Programmes -->
       <StatusBar Name="statusBar" Height="25" DockPanel.Dock="Bottom" />

        <!-- TabControll in der Mitte des Programmes -->
        <TabControl Name="tabControl_1" >
            
            <!-- Tab1 - Auswahl #1 -->
            <TabItem Header="Auswahl" Name="tabItem_Auswahl">
                <Grid>
                    <ComboBox Name="comboBox_TrafoAuswahl" Width="280" HorizontalAlignment="Center" VerticalAlignment="Center" Loaded="comboBox_TrafoAuswahl_Loaded" />
                </Grid>
            </TabItem>
            
            <!-- Tab2 - Auswahl #2 -->
            <TabItem Header="Auswahl 2" Name="tabItem_Auswahl2">
                
                <!-- DockPanel für Buttons und ListBox-->
                <DockPanel LastChildFill="True" >

                    <!-- StackPanel zum Ausrichten der Buttons / Erstes Element des DockPanel-->
                    <StackPanel>
                        <Button Name="bnt_AuswahlTrafo_OK" Content="OK" DockPanel.Dock="Top" Height="24" Width="76" Margin="5,10,5,5" />
                        <Button Content="Abbrechen" Height="24" Name="bnt_AuswhalTrafo_Abbruch" Width="76" Margin="5,5,5,5" />
                        <Button Content="Beenden" Height="24" Name="bnt_AuswahlTrafo_Beenden" Width="76" Margin="5,5,5,5" />
                    </StackPanel>

                    <!-- ListBox für Auswahl Trafo / Zweites Element des DockPanel-->
                    <ListBox>
                    </ListBox>
                
                </DockPanel>
            </TabItem>
            
            <!--Tab3 - IR -->
            <TabItem Header="IR" Name="tabItem_IR">
                <Grid />
            </TabItem>
        </TabControl>
         
    <!-- Ende DockPanel-->
    </DockPanel>
   
</Window>