none
Wert einer Variable korrekt über Konsolenausgabe, aber ohne Konsolenausgabe Sysntaxfehler RRS feed

  • Frage

  • Hallo allerseits,

    1)

    Ein aus einer Datenbank ausgelesener Datensatz (diese befinden sich in einem Datensatzrecord) soll an der Zeile i ausgelsen und dort die Spalte mit dem Spaltennname "Integer" ausgelesen werden.

    Wenn man dies wie folgt auf der Konsole ausgibt, funktioniert alles: Console::Write("Wert [zeile][1]={0};", row[table->Columns->IndexOf("Integer")]);

    Console::Write("Wert der Spalte 1= {0};", row[table->Columns[1]]);

    Wenn ich dies aber wie folgt mache:

    int z1;

    z1=row[table->Columns->IndexOf("Integer")];

    kommt die Fehlermeldung:

    "error C2440: '=': 'System::Object ^' kann nicht in 'int' konvertiert werden

    Es ist kein benutzerdefinierter Konvertierungsoperator verfügbar, oder

    Es gibt keinen Kontext, in dem diese Konvertierung möglich ist" Frage:

    Was muß ich machen, damit mein Versuch über einen _Index_ der in eckigen Klammern steht, den Datensatz anzusprechen bzw. das Datum in der Spalte über einen Index anzusprechen ?

    mfg

    Bh

     

    2) Hier ein Auszug aus dem Programm: void SampleTable::ListData()

    {

                Console::WriteLine(L"List Table Data.");

                auto connection = _accessDb->GetOleDbConnection();

                try

                {

                            auto adapter = gcnew OleDbDataAdapter(

                                       L"SELECT Id, [Integer] FROM Tabelle;",

                                       connection);

     

                            auto table = gcnew DataTable(L"Tabelle");

                            adapter->Fill(table);

     

                            // Column Names

                            for each (DataColumn^ column in table->Columns)

                            {

                                       Console::Write("{0};", column->ColumnName);

                            }

                            Console::WriteLine("");

     

                            // Data Rows

                            for each (DataRow^ row in table->Rows)

                            {

                                       for each (DataColumn^ column in table->Columns)

                                       {

                                                   Console::Write("{0};", row[column]);

                                       }

                                       Console::WriteLine();

     

                                       //Testbeginn

                                        

                                       Console::Write("Wert [zeile][1]={0};", row[table->Columns->IndexOf("Integer")]);

                                       int z1;

                                       z1=row[table->Columns->IndexOf("Integer")];

                                        Console::WriteLine();

     

                                       Console::Write("Wert der Spalte 1= {0};", row[table->Columns[1]]);

                                       Console::WriteLine();

                                       int z2;

                                       //z2=row[table->Columns[1]];



     

    Dienstag, 15. November 2016 18:38

Antworten

  • Hallo bh,

    im allgemeinen ist for each (in .NET) vorzuziehen, aber Du kannst auch über Indizes darauf zugreifen, denn die Rows Auflistung und die DataRow haben jeweils einen Standard-Indexer (wie jede IList).

    Ich habe das ursprüngliche Beispiel erweitert. In TableAsCSV findest Du beides (for each auskommentiert), wobei die Daten in eine List<T> übertragen werden. Danach auf der Konsole - als Spielerei via Action Delegaten - und optional in eine Datei (Pfad muss ggf. in Program angepasst werden).

    Ich habe es als (sehr rudimentäre) CSV Konvertierung implementiert - wobei die List<T> nicht notwendig wäre, sondern nur (ein Minimum an) Möglichkeiten aufzeigen soll.

    Ganz allgemein zu C++/CLI:

    Die Erfahrungen damit machen Dich weder zu einen C++ Programmierer noch zu einem Kenner des .NET Frameworks. C++/CLI dümpelt seit Jahren vor sich hin und daran wird sich kaum was ändern, da man derzeit dafür die C++ Standards implementiert - was mit CLI gar nichts zu tun hat.

    Eine umfassende Nutzung der .NET Möglichkeiten ist damit nicht zu realisieren. Dazu braucht man C# (oder F#, Visual Basic). Auch Java hat mit Java 7/8 nachgezogen, um vergleichbare Möglichkeiten anzubieten, die .NET seit einigen Jahren hat.

    Gruß Elmar

    Freitag, 18. November 2016 09:43

  • Hallo Elmar,
    habe die Lösung selbst gefunden:

    auto adapterTemp = gcnew OleDbDataAdapter(
          L"SELECT COUNT(*) FROM Tabelle;",
          connection);

    auto tableTemp = gcnew DataTable(L"Tabelle");
    adapterTemp->Fill(tableTemp);
    Int32 anzahl;
    Object^ value = tableTemp->Rows[0][0];
    anzahl = safe_cast<Int32>(value);

    mfg
    Bh

    Sonntag, 20. November 2016 12:58

Alle Antworten

  • Hallo Bernhart,

    nuja, Console::Write erwartet einen String, den kann C++ wohl eigenständig aus deinem angegeben Konstrukt

    row[table->Columns[1]]

    erzeugen. Um aus dem Wert aber eine Zahl zu machen, musst Du den Rückgabewert schon selbst umwandeln.

    In C# wäre das bspw.: mit:

    int i;
    int.TryParse( "123", out i );
    

    möglich. Wie das in C++ geht, kann ich dir nicht sagen aber konvertieren musst Du es halt trotzdem.



    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    Dienstag, 15. November 2016 21:33
  • Hallo Bh,

    Eine DataTable.Rows Auflistung liefert immer nur System::Object zurück, um auf den richtigen Typ zu kommen ist ein safe_cast erforderlich, für oben auf ein System::Int32.

    Weil DataSet/DataTable aus den Tagen von .NET 1.x stammen, gab es dort noch keine Generics. Die kann man mit LINQ To DataSet nachrüsten, so z. B. mit den DataRowExtensions.

    Gruß Elmar

    Dienstag, 15. November 2016 21:37
  • Hallo Elmar,
    Danke für deine Hilfe,

    >
    >Eine DataTable.Rows Auflistung liefert immer nur System::Object zurück,
    >um auf den richtigen Typ zu kommen ist ein safe_cast erforderlich,
    >für oben auf ein System::Int32.
    >
    >
    >Weil DataSet/DataTable aus den Tagen von .NET 1.x stammen, gab es dort noch keine
    >Generics. Die kann man mit LINQ To DataSet nachrüsten, so z. B. mit den 
    >DataRowExtensions.
    >

    1)
    Was meinst du mit nachrüsten ?
    Einbinden einer Bibliothek o.ä.
    Wie wird das konkret gemacht ?

    2)
    Gab es in den alten Tagen von .NET1.x nicht die Möglichkeit in DataSet/DataTable auf einen Datensatz bzw. eine Spalte eines Datensatzes zuzugreifen ?

    Ich bin an einem einfachen Beispiel interessiert, damit ich meein Vorhaben realisieren kann.

    Auch in den alten Tagen von .NET1.x kann es doch nicht so gewesen sein, daß man _nur_ mit Console auf einen Datensatz bzw. die Spalte eines Datensatzes zugreifen konnte.
    Wie wurde das dann dort gelöst ?

    mfg
    Bh

    Mittwoch, 16. November 2016 13:01
  • Hallo Bernhart,

    steht doch alles da? Du musst den Wert konvertieren.


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    Mittwoch, 16. November 2016 13:20
  • Hallo Stefan,

    >
    >Du musst den Wert konvertieren.
    >
    1)
    Aber wie?
    Wenn ich z.B: schreibe:
    DataRowExtensions^ d;
    oder
    DataRowExtensions d;
    meldet der Compiler:
    'DataRowExtensions': nichtdeklarierter Bezeichner

    2)
    Kannst du mir konkret ein paar Codeschnipsel geben?
    Das würde mir hoffentlich weiterhelfen.

    mfg
    Bh


    Mittwoch, 16. November 2016 18:01
  • Hallo bh,

    zum safe_cast, habe ich das Beispiel aus SampleTable::ListData erweitert:

        Int64 integerSum = 0;
        Decimal decimalSum = 0;
        for each (DataRow^ row in table->Rows)
        {
            for each (DataColumn^ column in table->Columns)
            {
                String^ columnName = column->ColumnName;
                Object^ value = row[column];
                if (columnName == L"Integer")
                {
                    // implizite Konvertierung Int32 -> Int64
                    integerSum = integerSum + safe_cast<Int32>(value);
                }
                else if (columnName == L"Decimal")
                {
                    decimalSum = decimalSum + safe_cast<Decimal>(value);
                }
                Console::Write("{0};", row[column]);
            }
            Console::WriteLine();
        }
        Console::WriteLine("{0} rows.", table->Rows->Count);
        Console::WriteLine("Summe Integer: {0}, Decimal: {1}", integerSum, decimalSum);
    

    zu den DataRowExtensions:

    Ich hätte sie besser gar nicht erwähnen sollen, denn eine wirkliche C++/CLI Unterstützung von LINQ als Basistechnologie, ist schlicht gesagt nicht vorhanden.  Es scheitert bereits an Basisdingen, wie Verwenden von Lambda-Ausdrücken mit verwalteten Typen (C++/CLI). Mehr siehe: Using lambdas - C++ vs. C# vs. C++/CX vs. C++/CLI.

    Willst Du etwas schreiben wie z. B. in C#

                int integerMax = table.AsEnumerable().Max((r) => r.Field<int>(table.Columns["Integer"]));
                Console.WriteLine("Max Integer: {0}", integerMax);
    

    musst Du letztendlich eine Sprache mit nativer Unterstützung verwenden - also C#, F# oder Visual Basic. Was Dir nicht nur dabei einiges unnötige Gefummele erspart, wenn es um .NET geht. Willst Du die Möglichkeiten von C++ haben, verweise ich auf STL und Co., da ist aber die Unterstützung des .NET Framework eher mager...

    Gruß Elmar

    Donnerstag, 17. November 2016 08:33
  • Hallo Elmar,
    vielen Dank für deine Mühe und deinen Aufwand.
    In C++ scheint mein Vorhaben mit viel Fummelei verbunden zu sein.
    In Java ist das z.B. viel einfacher (vermutlich auch in C#).
    Leider muß ich dieses kleine Projekt von mir in C++ machen (aber dann ist Schluss damit).
    Deswegen noch eine letzte Frage:
    Wie kann ich die Menge der Datensätez table->Rows in ein eindimensionales Feld umwandeln (casten),
    so daß man dort einfach auf ein Element dieses Feldes zugreifen kann.
    Ich phantasiere jetzt einfach mal zusammen:
    Feld values[] = safe_cast<Feld> (table->Rows);
    wobei es den Datentyp Feld vermutlich nicht gibt.
    Man kann dann z.B. mit values[1] auf das 1. Element des Feldes zugreifen (1. Datensatz).
    Hast du da eine Idee?

    mfg
    Bh


    Donnerstag, 17. November 2016 17:55
  • Hallo Bernhart,

    ich befürchte, Du (oder ich^^) hast da ein Verständnisproblem.

    Eine Tabelle besteht aus Spalten, Zeilen und den Werten in jeder Spalte/Zeile Kombination.

    ID   Name     Wert
    1    Test 1   Wert 1
    2    Test 2   Wert 2
    3    Test 3   Wert 3

    Wenn Du nun daraus was eindimensionales machen willst, kannst Du eigentlich nur die Werte pro Zeile zusammenfassen. Im Ergebnis also:

    1, Test 1, Wert 1
    2, Test 2, Wert 2
    3, Test 3, Wert 3

    wobei ich bei der Zusammenführung einfach mal das Komma als Separator verwendet habe.

    Damit kannst Du aber natürlich nicht mehr auf das 1., 2., 3., ... Element der Zeile zugreifen.

    Daher nochmal die Frage: Was genau willst Du eigentlich damit? Poste doch bitte mal ein Beispiel für deine Ausgangstabelle, das gewünschte Ergebnis der Operation und beschreib detaillierter anhand der Beispiele, wie Du wo auf was in welcher Form zugreifen willst.

     


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community


    • Bearbeitet Stefan Falz Donnerstag, 17. November 2016 18:32
    Donnerstag, 17. November 2016 18:31
  • Hallo Stefan,
    I)

    Ich will einfach nur auf (ohne for each) direkt auf die "Elemente" der Tabelle zugreifen.
    Dazu gibt es meiner Vorstellung nach 2 Möglichkeiten:
    1. Möglichkeit)
    Wenn man die von dir angegebe Tabelle in ein 2-dimensionales Feld feld2 umwandelt,
    kann man z.B. mit
    x = feld2[0][2]
    direkt auf eine Spaltenwert zugreifen und laut deinem Beispiel hat dann x den Wert
    Wert 1

    2. Möglichkeit)
    Wenn man die von dir angegebe Tabelle in ein 1-dimensionales Feld feld1 umwandelt, besteht feld1 aus 3 Zellen:
    Zelle0=feld1[0] =   1    Test 1   Wert 1
    Zelle1=feld1[1] =   2    Test 2   Wert 2
    Zelle2=feld1[2] =   3    Test 3   Wert 3

    Man kann dann z.B. mit
    y = feld1[0]
    direkt allerdings nur auf den ganzen Datensatz zugreifen und laut deinem Beispiel hat dann y den Wert
    1 Test 1 Wert 1
    Jedes Element feld1[i] ist also ein Datensatz.
    Und jetzt braucht man dann dann noch ein Werkzeug, um auf einen Spaltenwert dieses Datensatzes y zuzugreifen.

    II)
    Mit
    auto table = gcnew DataTable(L"Tabelle");
    adapter->Fill(table);
    hat Elmar so eine Menge von Datensätzen in der Variable table ausgelesen.
    Jetzt brauche ich nur noch ein paar Zeilen Code um in table - ohne for each - direkt auf eine Spalte zugreifen zu können.
    Meine Idee war eben table entweder in ein 1- oder 2- dimensionales Feld umzuwandeln und dann
    - wie oben angeführt - auf eine Spalte zugreifen zu können.

    Frage:
    Wie kann man dies in C++ ohne for each realisieren ?

    PS:
    Das läuft auf das Problem raus:
    Wie kann man den Vorschlag von Elmar nicht mit for each, sondern dem "alten" for realisieren,
    also in der Form:
    for(i=0;i<....;i++){
      for(j=0;j<....; j++){
        ....
      }
    }

    mfg
    Bh
    Freitag, 18. November 2016 07:57
  • Hallo bh,

    im allgemeinen ist for each (in .NET) vorzuziehen, aber Du kannst auch über Indizes darauf zugreifen, denn die Rows Auflistung und die DataRow haben jeweils einen Standard-Indexer (wie jede IList).

    Ich habe das ursprüngliche Beispiel erweitert. In TableAsCSV findest Du beides (for each auskommentiert), wobei die Daten in eine List<T> übertragen werden. Danach auf der Konsole - als Spielerei via Action Delegaten - und optional in eine Datei (Pfad muss ggf. in Program angepasst werden).

    Ich habe es als (sehr rudimentäre) CSV Konvertierung implementiert - wobei die List<T> nicht notwendig wäre, sondern nur (ein Minimum an) Möglichkeiten aufzeigen soll.

    Ganz allgemein zu C++/CLI:

    Die Erfahrungen damit machen Dich weder zu einen C++ Programmierer noch zu einem Kenner des .NET Frameworks. C++/CLI dümpelt seit Jahren vor sich hin und daran wird sich kaum was ändern, da man derzeit dafür die C++ Standards implementiert - was mit CLI gar nichts zu tun hat.

    Eine umfassende Nutzung der .NET Möglichkeiten ist damit nicht zu realisieren. Dazu braucht man C# (oder F#, Visual Basic). Auch Java hat mit Java 7/8 nachgezogen, um vergleichbare Möglichkeiten anzubieten, die .NET seit einigen Jahren hat.

    Gruß Elmar

    Freitag, 18. November 2016 09:43

  • Hallo Elmar,
    vielen, herzlichen Dank für dein Feedback.
    Dadurch konnte ich mein kleines Projekt - bis auf eine Kleinigkeit - fertigstellen.

    Eine Frage habe ich noch:
    Wie kann man die Menge der Datensätze einer Tabelle bestimmen ?
    Die SQL-Abfrage lautet zwar:
    SELECT COUNT(*) FROM Tabelle
    Doch wie kann ich die Anzahl programmtechnisch bestimmen:
    Mein Versuch:
    // Bestimme Anzahl Datensätze der Tabelle
    auto adapterTemp = gcnew OleDbDataAdapter(
          L"SELECT COUNT(*) FROM Tabelle;",
          connection);
          auto tableTemp = gcnew DataTable(L"Tabelle");
          adapterTemp->Fill(tableTemp);
    // Wie geht es jetzt aber weiter ?

    mfg
    Bh



    Sonntag, 20. November 2016 08:23

  • Hallo Elmar,
    habe die Lösung selbst gefunden:

    auto adapterTemp = gcnew OleDbDataAdapter(
          L"SELECT COUNT(*) FROM Tabelle;",
          connection);

    auto tableTemp = gcnew DataTable(L"Tabelle");
    adapterTemp->Fill(tableTemp);
    Int32 anzahl;
    Object^ value = tableTemp->Rows[0][0];
    anzahl = safe_cast<Int32>(value);

    mfg
    Bh

    Sonntag, 20. November 2016 12:58
  • Hallo Bh,

    kann man so machen, üblicherweise würde man aber ExecuteScalar verwenden um einen einzelnen Wert abzurufen, das hat weniger Overhead:

        int AccessDatabase::GetRowCount(String ^ tableName)
        {
            if (String::IsNullOrWhiteSpace(tableName))
                throw gcnew ArgumentNullException("tableName");
    
            auto connection = GetOleDbConnection();
            try
            {
                connection->Open();
    
                // positionelle Parameterreihenfolge!
                auto countCommand = gcnew OleDbCommand(
                    String::Format(L"SELECT COUNT(*) FROM [{0}];", tableName),
                    connection);
    
                Object^ rowCount = countCommand->ExecuteScalar();
                // not for COUNT but otherwise possible
                if (rowCount == DBNull::Value)
                    return -1;
                return safe_cast<int>(rowCount);
            }
            finally
            {
                delete connection;
            }
        }
    

    und dann verwenden als:

        int rowCount = _accessDb->GetRowCount("Tabelle");
        Console::WriteLine(L"Tabelle has {0} rows.", rowCount);
    
    Gruß Elmar
    Sonntag, 20. November 2016 14:13