none
Fehler bei Abfragen mit ADO.NET RRS feed

  • Frage

  • Ich versuche mich grad an einer (VB-)Anwendung, die per ADO auf eine SQL-Datenbank im Format *.sdf zugreift.

    Gegeben ist folgender Code (VS 2010)*:

    Public Class Database
      Implements IDisposable
    
      Private _conn As ADODB.Connection
    
      Public Sub New()
        _conn = New ADODB.Connection
        _conn.Open("Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=.\Arztrechnungen.sdf;")
        _conn.CursorLocation = ADODB.CursorLocationEnum.adUseClient
      End Sub
    
      Public ReadOnly Property Connection As ADODB.Connection
        Get
          Return _conn
        End Get
      End Property
    
      '... (IDispose-Implementierung weggelassen)
    
    End Class
      Private Sub ZeigeOffeneRechnungen()
    
        Dim sql As String = "SELECT Rechnungen.ID, Aerzte.Name, Aerzte.Kurzname, Rechnungen.Rechnungsnummer, Rechnungen.Rechnungsdatum, Rechnungen.Zahlungsinformationen, " & _
                            "Rechnungen.Rechnungsbetrag, Rechnungen.DatumErinnerung, Rechnungen.DatumMahnung1, Rechnungen.DatumMahnung2, Rechnungen.DatumMahnung3, " & _
                            "Rechnungen.Mahnbetrag, Rechnungen.BetragBezahlt, Rechnungen.Kontonummer, Rechnungen.BLZ, Rechnungen.DatumBezahlt, LA_Beihilfe.DatumErstellt AS DatumBeihilfeErstellt, " & _
                            "LA_Beihilfe.DatumErstattet AS DatumBeihilfeErstattet, LA_Versicherung.DatumErstellt AS DatumVersicherungErstellt, LA_Versicherung.DatumErstattet AS DatumVersicherungErstattet " & _
                            "FROM Rechnungen LEFT OUTER JOIN " & _
                            "Leistungsantraege AS LA_Versicherung ON Rechnungen.LeistungsantragIDVersicherung = LA_Versicherung.ID LEFT OUTER JOIN " & _
                            "Leistungsantraege AS LA_Beihilfe ON Rechnungen.LeistungsantragIDBeihilfe = LA_Beihilfe.ID LEFT OUTER JOIN " & _
                            "Aerzte ON Rechnungen.ArztID = Aerzte.ID"
    
        Try
          Using db As New Database
            Try
              Dim rs = db.Connection.Execute(sql)
              Do While Not rs.EOF
                '...
                rs.MoveNext()
              Loop
              rs.Close()
            Catch ex As Exception
              Dim errs = db.Connection.Errors
              Stop
            End Try
          End Using
        Catch ex As Exception
          Stop
        End Try
    
      End Sub
    

    Der Fehler tritt in der Zeile "Do While Not rs.EOF" (nicht bei Execute()!) auf. Die Exception besagt nur "Der Datenprovider oder ein anderer Dienst gab den Status E_FAIL zurück."

    Wenn ich die Errors-Collection des Connection-Objekts auswerte, finde ich dort auch nur ein Fehler-Objekt, das genau dasselbe besagt, und dass die Error-Source "Microsoft Cursor Engine" zu sein scheint.

    Wenn ich die Prozedur jedoch schrittweise debugge und mir vor dem Ausführen der Abfrage der Eigenschaft "rs.EOF" den Recordset näher angesehen habe (z.B. mit "?rs" im Direktfenster), tritt dieser Fehler nicht mehr auf und die Schleife läuft wie gewünscht ohne Exception durch.

    Gibt's hier ein Timing-Problem? Oder wie kann ich den Fehler vermeiden?

    *) Da die Entwicklung sich noch im Prototyping-Stadium befindet habe ich auf eine ausführlichere Exception-Auswertung im Code vorerst verzichtet.

    PS: Hab eben nochmal ein bisschen rumexperimentiert und dabei jetzt sogar den Fall gehabt, dass er zwar in den Catch-Block verzweigt, mein Exception-Objekt "ex" dort aber "Nothing" ist. Hä?

    Freitag, 20. Juli 2012 11:51

Alle Antworten

  • die Wahrscheinlichkeit, dass die .SDF Datei nicht gefunden ist relativ hoch.

    Versuch es mal mit einem absolutem Pfad zur SDF Datei und wenn es dann funktioniert, kannst Du immer noch mit relativen Pfaden probieren und dabei die SDF Datei an den richtigen Ort kopierst oder den relativen Pfad anpasst.


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.

    Freitag, 20. Juli 2012 13:27
  • Ich kann es gerne ausprobieren, aber ich halte es für unwahrscheinlich, dass es am relativen Pfad zur Datenbank liegt. Denn andere (vor allem einfachere!) Abfragen (z.B. "Select * From Rechnungen") funktionieren mit diesem Prinzip problemlos.

    Ich habe in der Zwischenzeit getestet, wie es sich mit serverseitigen Cursorn verhält und durfte dabei feststellen, dass bei der Verwendung von adUseServer (statt adUseClient), also dem Verzicht auf einen frei positionierbaren Cursor, dieses Verhalten nicht mehr auftritt, ich also problemlos Zugriff auf das Recordset erhalte.

    Leider habe ich damit dennoch keine Erklärung für das oben beschriebene Verhalten, auch wenn ich - zumindest in den meisten Fällen - auf diesen "Workaround" ausweichen kann, denn so oft brauche ich keine Recordsets, in denen ich auch rückwärts navigieren kann oder bei denen ich die Gesamtzahl der Datensätze benötige ohne das Recordset einmal komplett durchlaufen zu müssen. Und im Zweifel kann ich die Anzahl auch per SQL abfragen - dann muss ich eben zwei Abfragen an die Datenbank senden.

    Freitag, 20. Juli 2012 15:50
  • Hallo Ralf,

    gibt es einen Grund, warum Du mit COM und nicht mit .NET arbeitest? Dein Code hat mit ADO.NET gar nichts zu tun. ADODB.Connection, ADODB.Recordset, ... brauchst Du nicht mehr und solltest das in .NET Anwendungen auch nicht verwenden.

    <ADODBConnection>.Execute für die Rückgabe eines Recordsets ist zudem so ziemlich die allerschlechteste Wahl, die man treffen konnte. Hierfür gibt es <ADODBRecordset>.Open(<SqlStatement>, <Connection>, 3, 1 ). Über diese Parameter kannst Du auch steuern, wie dein Recordset durchlaufen werden kann.

    Siehe dazu bspw.:

      http://www.aspfaq.de/index.asp?FID=4&ELE=354

      http://msdn.microsoft.com/en-us/library/windows/desktop/ms675544(v=vs.85).aspx


    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


    Freitag, 20. Juli 2012 15:53
    Moderator
  • Ich kann es gerne ausprobieren, aber ich halte es für unwahrscheinlich, dass es am relativen Pfad zur Datenbank liegt. Denn andere (vor allem einfachere!) Abfragen (z.B. "Select * From Rechnungen") funktionieren mit diesem Prinzip problemlos.

    diese Information hatte in Deinem Posting gefehlt.

    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.

    Freitag, 20. Juli 2012 16:08
  • Dein Code hat mit ADO.NET gar nichts zu tun.

    Mein Fehler. Ich hab mich davon irreleiten lassen, dass die in VS.NET bereitgestellte ADODB-Bibliothek unter den .NET-Komponenten zu finden war. Ich denke ich bin nun schlauer. Aber leider nicht genug, um das eigentliche Problem, das ich mit der Verwendung obigen Codes lösen wollte, zu beheben.

    Im Kern geht es mir darum, dass ich in meinen Datentabellen ID-Felder mit Autowert (Autoincrement) verwenden will. Sprich: Wird ein neuer Datensatz hinzugefügt, soll automatisch eine neue eindeutige ID (vom Typ Int32) vergeben werden, über die ich den Datensatz zu jeder Zeit identifizieren/referenzieren kann. Und genau da liegt mein Problem: Ich habe es zwar immer brav hinbekommen - sowohl mit DataSets, dem Entity-Framework als auch mit dem SqlCeResultSet-Modell - neue Datensätze hinzuzufügen, die jeweils eine automatisch vergebene ID bekommen haben, aber im Gegensatz zu dem früheren ADODB (bei dem ich das in der neuen Version aber auch nicht mehr geschafft habe) habe ich keine Möglichkeit gefunden, wie ich die ID des gerade angelegten Datensatzes denn nun lesen und nachfolgend weiternutzen kann.

    Ein simples Beispiel mit SqlCeResultSet, Die Tabelle "TestTab" enthält zwei Spalten: ID (int, autoincrement, primary key), Nummer (int)

        conn = New SqlCeConnection(ConnectionStringSqlCe)
        conn.Open()
        Using cmd As New SqlCeCommand("Select * from testtab", conn)
          rs = cmd.ExecuteResultSet(ResultSetOptions.Updatable Or ResultSetOptions.Scrollable Or ResultSetOptions.Sensitive)
        End Using
    
        Dim newrec As SqlCeUpdatableRecord = rs.CreateRecord()
        newrec("nummer") = 100
        rs.Insert(newrec)
    

    Das Beispiel wird problemlos ausgeführt. Aber woher weiß ich nun, welche ID der gerade angefügte Datensatz hat?

    Theoretisch könnte ich den Command erneut ausführen. Ich hab aber auch festgestellt, dass ich mit den Read*-Methoden weiterhin direkt aus der Datenbank lesen kann - da stehen die vergebenen IDs auch drin. Bringt mich aber nicht wirklich weiter, wenn ich nicht zweifelsfrei den hinzugefügten Datensatz identifizieren kann. Es könnte ja auch weitere Datensätze geben, die in dem einzigen nicht-autoincrement-Feld dieselben Werte (hier 100) stehen haben. Desweiteren muss der neu dem ResultSet hinzugefügte Datensatz ja nicht zwangsläufig am Ende stehen - das ResultSet könnte ja durch ein SELECT-Statement mit ORDER BY-Klausel entstanden sein.

    Wie gesagt: Das alte ADO hat es mir da sehr viel einfacher gemacht: <RecordSet>.AddNew erzeugte mir einen neuen Datensatz, den ich brav füllen und dann mit <RecordSet>.Update in die Datenbank schreiben konnte. Nach dieser Aktion stand der Cursor im Recordset automatisch auf dem gerade neu hinzugefügten Datensatz, über den ich dann die von der DB vergebene ID auslesen konnte.

    Hat zwar mit dem Problem im Ausgangsposting (und dem Thread-Thema) nicht mehr wirklich was zu tun, ist aber letztendlich der Grund, warum ich hier auch mit solchen veralteten Konstrukten herum experimentiere.

    Donnerstag, 26. Juli 2012 14:32
  • Hallo Ralf,

    siehe dazu:

      http://social.technet.microsoft.com/Forums/de-DE/sqlserver/thread/eb5c1fde-3082-4e84-9b90-84cb78bc6060/


    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

    Donnerstag, 26. Juli 2012 14:56
    Moderator
  • Hallo Ralf,

    siehe dazu:

      http://social.technet.microsoft.com/Forums/de-DE/sqlserver/thread/eb5c1fde-3082-4e84-9b90-84cb78bc6060/

    Hallo Stefan,

    laut dem verlinkten Beitrag ist also "SELECT SCOPE_IDENTITY()" das Werkzeug meiner Wahl? So deute ich das jedenfalls. Leider - abgesehen davon dass ich das etwas umständlich finde, aber das ließe sich zur Not in einer speziellen Klasse automatisieren - funktioniert es nicht:

    Die Funktion wird von SQL Server Compact Edition nicht erkannt. [ Name of function = SCOPE_IDENTITY,Data type (if known) = ]


    Donnerstag, 26. Juli 2012 15:32
  • Hallo Ralf,

    ich persönlich habe in meinem O/R Mapper (allerdings was selbstgeschriebenes, da keiner der verfügbaren O/R Mapper das bietet, was ich haben will) eine entsprechende Methode, die mir das automatisiert. Für normale Arbeiten muss man das nur hinter das INSERT Statement schreiben und das Statement mit ExecuteScalar anstelle von ExecuteNonQuery absetzen. So viel umständliches sehe ich da nicht.

    Aber Du hast natürlich Recht, bei SQL Server CE gibts das in dieser Form nicht. Ich hatte das "CE" hinter SQL Server übersehen/vergessen/ignoriert/... :)

    Für CE schreib @@IDENTITY anstelle von SCOPE_IDENTITY(). Für Access wäre das ebenso. Aber bitte nicht im richtigen SQL Server verwenden, das führt unweigerlich zu Problemen, wenn bspw. ein Trigger einen Datensatz in eine andere Tabelle schreibt, die ebenfalls eine IDENTITY Spalte hat. 


    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


    Donnerstag, 26. Juli 2012 15:44
    Moderator
  • ich persönlich habe in meinem O/R Mapper (allerdings was selbstgeschriebenes, da keiner der verfügbaren O/R Mapper das bietet, was ich haben will) eine entsprechende Methode, die mir das automatisiert. Für normale Arbeiten muss man das nur hinter das INSERT Statement schreiben und das Statement mit ExecuteScalar anstelle von ExecuteNonQuery absetzen. So viel umständliches sehe ich da nicht.

    Ich nehme mal an, O/R-Mapper meint Hilfsklassen für die Verbindung (O)bjektorientierter Klassen mit (R)elationalen Datenbanken, also das was VS in Form von per Assistent generierten DataSets oder dem Entity Framework anbietet. Auch ich sehe mich da in der Situation, dass die mitgelieferten nicht ganz das erfüllen was ich mir wünschen würde, und bin schon vor einiger Zeit darauf gekommen, eigene Klassen zu programmieren, die mir u.a. auch die Sache mit der ID abnehmen sollen und mir dabei helfen, typisierte Datenklassen schnell und einfach abzubilden.

    Und du hast natürlich auch recht, dass es bei dieser Vorgehensweise sooo umständlich nicht ist, aber ich habe zurzeit das Gefühl bei meinem Wiedereinstig in die Datenbankprogrammierung quasi wieder von vorne zu beginnen. Ich glaube mein derzeitiges "Problem" wäre eigentlich gar keines, wenn das im Beispiel genannte SqlCeResultSet sich beim Anfügen neuer Datensätze wie das alte ADODB.RecordSet verhielte, d.h. wenn nach einem Insert() der Cursor automatisch auf dem gerade hinzugefügten Datensatz stände.

    Was SCOPE_IDENTITY() und @@IDENTITY angeht, überlege ich grad sogar, meinen "O/R-Mapper"(?) so zu gestalten, dass er entweder abhängig von den Verbindungseinstellungen das jeweils passende auswählt oder einfach stur erst SCOPE_IDENTITY() probiert und, falls das fehlschlägt, dann auf @@IDENTITY ausweicht.

    Vielen Dank jedenfalls erstmal für deine Hilfe.

    Donnerstag, 26. Juli 2012 19:20