none
CursorAdapter ADO Update

    Frage

  • Hallo,

    Ich verwende per ADODB eine SQLSERVER.CE-Datenbank. Ich kann die Tabellen darin auch per CursorAdapter lesen (und per SQL-Kommandos direkt beschreiben). Bislang hab ich es aber noch nicht hingekriegt, die auch per CursorAdapter zu beschreiben bzw. Updates zu machen. Bei meinen Versuchen bekomme ich (mit unterschiedlichem Code) entweder die Meldung, das verwendete ADODB Recordset sei schreibgeschützt oder einen Microsoft Cursor Engine Fehler oder aber das Programm stürzt komplett ab. Was muss ich am Recordset oder am CA richtigerweise einstellen?

    Gruß,

    Winfried

    Dienstag, 15. Januar 2013 23:10

Alle Antworten

  • Ein CA mit ADODB-Recordset ist auch letzten endes nur am Cursor zu bedienen. Am REcordset solltest Du nichts tun, Cursor ändern und TABLEUPDATE().Was im CA gesetzt sein muß ist dasselbe wie im ODBC oder auch Native Modus: Key-Feld, Updatebare Felder.

    Muß ich in Ruhe mal ausprobieren. Der CA Builder sollte das nötige ADODB-Recordset schon selbständig erzeugen und dessen Eigenschaften auch schon richtig setzen.

    Tschüß, Olaf.

    Nachtrag: siehe http://soykanozcelik.wordpress.com/2011/04/10/how-to-display-sdf-file-content-via-vfp-cursoradapter/

    Für einen ADO Cursoradapter per VFPOLEDB.1 zur Northwind Database reicht, was der Builder von Foxpro setzt, aber der Builder erzeugt z.B. kein ADODB.Command



    Mittwoch, 16. Januar 2013 14:12
  • Hallo Olaf,

    meine Implementation entspricht praktisch der aus Deinem Nachtrags-Link, nur ohne die automatische Erzeugung der Feldlisten, da ich ja meine Felder kenne.

    Aber genau mit dem Code bekomme ich einen Totalabsturz ohne Fehlermeldung, sobald ich im Browser eine Zeile mit geändertem Wert verlasse.

    Gruß,

    Winfried

    Donnerstag, 17. Januar 2013 09:01
  • Ein Totalabsturz sollte in keinem Fall passieren, das weist eher auf einen fehler in der SDF Datei oder in der Installation des OLEDB Treibers hin.

    Du kannst ja noch probieren, was sich ändert, wenn Du Buffering einschaltest:

    CURSORSETPROP("Buffering",5,oCA.Alias)

    nach oCA.CursorFill() und bevor Du irgendeine Änderung an den Daten machst.

    Dann abschließend Tableupdate(2,.f.)

    Das darf .F. zurückgeben, wenn es scheitert und mehr infos in AERROR, aber keinen Totalabsturz verursachen. Hast Du einen Zweitrechner, wo Du's nochmal gegenprüfen kannst? eine andere SDF-Datei?

    Tschüß, Olaf.


    Donnerstag, 17. Januar 2013 10:53
  • Hallo Olaf,

    den Test auf einem 2. Rechner (unter XP) hab ich schon gemacht: gleiches Ergebnis.

    Die .SDF ist frisch erzeugt und funktioniert auch ansonsten.

    Schalte ich Buffering ein, so stürzt das Programm beim Tableupdate ab.

    Ich hab auf beiden Maschinen auch VS, auf dem XP-Rechner  VS2008. Dort meldet sich beim Programmabsturz der JIT-Debugger mit "Unbehandelte Win32-Ausnahme"

    Auf dem Win7-64-System bekomme ich ein Fehlerfenster "Lotex Executive File funktioniert nicht mehr" (Lotex heißt meine EXE). In den Problemdetails steht:

    Problemereignisname: BEX

    Fehlermodulname: MSVCR80.dll

    Fehlermodulversion: 8.0.50727.6229

    Wegen der MSVCR80.DLL, die ja eigentlich von VFP nicht verwendet wird,  hab ich das Programm auch mal über den Dependency Walker gestartet. Leider funktioniert der nur auf dem XP-System und dort kommt aber genau die Meldung nicht.

    Gruß,

    Winfried

    Donnerstag, 17. Januar 2013 11:52
  • Wenn dasselbe Problem bei beiden Rechnern besteht, dann ist die Wurzel allen übels evtl. im Download, den Du auf beiden Rechnern installiert hattest. Es klingt nämlich immer noch eher nach einem Treiber/Installationsproblem, selbst wenn ein Provider gar nicht da ist, würde VFP nur einen normalen abfangbaren OLE Fehler bekommen statt eines Totalabsturzes.

    Du kannst ja mal Deinen Code posten, dann probiere ich mal bei mir.

    Tschüß, Olaf.


    Freitag, 18. Januar 2013 08:12
  • Hallo Olaf,

    mein Original-Code ist in 2 Klassen eingebettet, aber ich hab probeweise jetzt wirklich exakt (bis auf den Tabellen- und den keyfield-Namen ) aus dem Beispiellink genommen. Die Tabelle selbst erzeuge ich vorher per

    CREATE Table Art (PRIMARY KEY (Artikelnr), [Artikelnr] nvarchar(18) not null, [Bezeichng] nvarchar(35) not null default '')

    und lege per INSERT INTO Art ein paar Daten rein und schließe die Datenbank wieder.

    Dann rufe ich die Procedure auf:

    PROCEDURE CaSqlCeTest
      Public oCA As CursorAdapter
      Local oConn As ADODB.Connection
      Local oRS As ADODB.Recordset
      Local oComm As ADODB.Command
      Local oErr As Exception
      LOCAL SqlFile
      Local cConnString
      SqlFile="D:\LotexDat.sdf"
      cConnString = "Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;"+;
      "Data Source="+m.SqlFile
      Try
        oConn = Createobject('ADODB.Connection')
        oConn.Open(cConnString)
        oComm = Createobject("ADODB.Command")
        oComm.ActiveConnection = oConn
        oRS = Createobject("ADODB.Recordset")
        oRS.Datasource.CursorLocation = 3 &&adUseClient
        oRS.Datasource.LockType = 3 &&adLockOptimistic
        oRS.ActiveConnection = oConn
        oCA=Createobject("CursorAdapter")
        oCA.DataSourceType = "ADO"
        oCA.Datasource = oRS
        oCA.MapBinary = .T.
        oCA.MapVarchar = .T.
        oCA.SendUpdates = .T.
        oCA.KeyFieldList = "Artikelnr" && Put the Key Field list here
        oCA.WhereType = 1 && Key Fields
        oCA.Alias = "CARI"
        oCA.Tables="Art" && <----- einziger Zusatz von mir
        oCA.SelectCmd = "SELECT * FROM Art"
        oCA.InsertCmdDataSourceType = "ADO"
        oCA.UpdateCmdDataSourceType = "ADO"
        oCA.DeleteCmdDataSourceType = "ADO"
        oCA.RefreshCmdDataSourceType = "ADO"
        oCA.InsertCmdDataSource = oComm
        oCA.UpdateCmdDataSource = oComm
        oCA.DeleteCmdDataSource = oComm
        oCA.RefreshCmdDataSource = oComm
        If !oCA.CursorFill(.F.,.F.,-1,oComm)
          Local laError
          Dimension laError[1]
          Aerror(laError)
          Messagebox(laError[2])
        Else
          Local laFlds,lcStr,lnFldCount,i
          Dimension laFlds[1]
          lnFldCount=Afields(laFlds)
          lcStr=""
          lcStr2 = ""
          For i = 1 To lnFldCount
            lcStr = lcStr + laFlds[m.i,1] + ","
            lcStr2 = lcStr2 + laFlds[m.i,1] + "  Art."+laFlds[m.i,1]+"," && There should be SPACE before CARI
          Endfor
          oCA.UpdatableFieldList = Left(lcStr , Len(lcStr )-1) && To remove last comma
          oCA.UpdateNameList = Left(lcStr2, Len(lcStr2)-1) && To remove last comma
          BROWSE
          IF TABLEUPDATE(2,.F.)  
            WAIT WINDOW "nach TABLEUPDATE"
          ELSE  
            AERROR(laErrors)
            MESSAGEBOX(laErrors[2])
          ENDIF
        Endif
      Catch To oErr
        Messagebox("ERROR "+ALLTRIM(STR(oErr.ErrorNo))+;
        " at Line "+ALLTRIM(STR(oErr.LineNo))+": "+oErr.Message)
      Endtry
    ENDPROC && CaSqlCeTest

    eine Änderung gegenüber dem Originalcode musste ich machen: Ohne oCA.Tables zu setzen, wird gar kein Update durchgeführt (und es gibt auch keinen Fehler). Nun aber, nach einer Änderung im BROWSE, gibts den Absturz.

    Das SDF-File kann ich übrigens problemlos per SQL Server Management Studio einsehen und damit arbeiten; das ist also OK. Auch aus VFP heraus kann ich per oConn.Execute alles mit der Datenbank machen. Nur mit dem CA da knallt's.

    Gruß,

    Winfried

    PS: Ich hab grad gesehen, dass hier im Forum beim Code-Einfügen VFP nicht mehr zur Auswahl steht. Das war doch mal anders, oder?

    Samstag, 19. Januar 2013 17:47
  • 1. Die Sprache Foxpro stand im Code Editor hier noch nie zur Wahl, nein.

    2. Ich kann den Fehler sogar nachvollziehen, in einer nicht gepufferten Tabelle passiert das bereits innerhalb des Browse beim Verlassen eines geänderten Datensatzes, wenn ich den Code genau so verwende.

    Ich würd's an Deiner Stelle nochmal mit dem CA Builder versuchen. Erstelle eine neue visuelle Klasse basierend auf CursorAdapter und dann starte den Builder per Rechtsklick im Class Designer, Menüpunkt Builder, so habe ich meinen CA gemacht.

    Tschüß, Olaf.

    Montag, 21. Januar 2013 10:28
  • Also noch einmal konkreter, was für mich funktioniert ist dem Code, den der CA Builder erzeugt noch folgenden Abschnitt im Init() der CA-Klasse zuzufügen:

    *** End of Select connection code: DO NOT REMOVE
    
    Local oComm
    oComm = Createobject("ADODB.Command")
    oComm.ActiveConnection = loConnDataSource
    
    This.InsertCmdDataSourceType = "ADO"
    This.UpdateCmdDataSourceType = "ADO"
    This.DeleteCmdDataSourceType = "ADO"
    This.RefreshCmdDataSourceType = "ADO"
        
    This.InsertCmdDataSource = oComm
    This.UpdateCmdDataSource = oComm
    This.DeleteCmdDataSource = oComm
    This.RefreshCmdDataSource = oComm
    
    *** Setup code: DO NOT REMOVE

    Die Kommentarzeilen am Anfang und Ende sind vom Builder und haben im Normalfall nichts dazwischen.

    Im Builder-Dialog habe ich in der Hauptsache gesetzt:

    1. Data source Type ADO und Use Connection String

    2. Select Command Select id,feld1 FRom table, Option Precompile SQL on backend server aus. Buffermode Override Optimisitc Table buffering

    3. Send updates / Autoupdate, Keyfield markeirt als Keyfield nicht updatebar, andere Felder updatebar aber nicht keyfield. Update per SQL Update, Key field only.

    Tschüß, Olaf.

    Montag, 21. Januar 2013 12:43
  • Hallo Olaf,

    danke, dass Du das auch mal durchgetestet hast.

    Ich hab aber leider auch beim CA-Builder-Code den Fehler, nach dem TableUpdate, wenn vorher ne Änderung war.

    Ich bin wieder von der gleichen Test-SDF ausgegangen.

    Der CA-Builder mit den Einstellungen von Dir und dem Zusatz-Code ergibt diesen Klassencode:

    DEFINE CLASS catest AS cursoradapter
        Tag = "Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=D:\LotexDat.sdf;"
        Height = 22
        Width = 23
        UseDeDataSource = .F.
        SelectCmd = "select Artikelnr, Bezeichng from Art"
        CursorSchema = "ARTIKELNR C(18), BEZEICHNG C(35)"
        Alias = "cari"
        BufferModeOverride = 5
        DataSourceType = "ADO"
        Flags = 0
        FetchMemo = .F.
        Prepared = .T.
        FetchSize = -1
        WhereType = 1
        KeyFieldList = "ARTIKELNR"
        Tables = "Art"
        UpdatableFieldList = "BEZEICHNG"
        UpdateNameList = "ARTIKELNR Art.Artikelnr, BEZEICHNG Art.Bezeichng"
        Name = "catest"
        PROCEDURE Init
            *** Setup code: DO NOT REMOVE
            local llReturn
            do case
                case not pemstatus(This, '__VFPSetup', 5)
                    This.AddProperty('__VFPSetup', 0)
                case This.__VFPSetup = 1
                    This.__VFPSetup = 2
                case This.__VFPSetup = 2
                    This.__VFPSetup = 0
                    return
            endcase
            set multilocks on
            llReturn = dodefault()
            *** End of Setup code: DO NOT REMOVE
            *** Select connection code: DO NOT REMOVE
            local loConnDataSource
            loConnDataSource = createobject('ADODB.Connection')
            ***<DataSource>
            loConnDataSource.ConnectionString = [Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=D:\LotexDat.sdf;]
            ***</DataSource>
            loConnDataSource.Open()
            This.DataSource = createobject('ADODB.RecordSet')
            This.DataSource.CursorLocation   = 3  && adUseClient
            This.DataSource.LockType         = 3  && adLockOptimistic
            This.DataSource.ActiveConnection = loConnDataSource
            *** End of Select connection code: DO NOT REMOVE
            Local oComm
            oComm = Createobject("ADODB.Command")
            oComm.ActiveConnection = loConnDataSource
            This.InsertCmdDataSourceType = "ADO"
            This.UpdateCmdDataSourceType = "ADO"
            This.DeleteCmdDataSourceType = "ADO"
            This.RefreshCmdDataSourceType = "ADO"           
            This.InsertCmdDataSource = oComm
            This.UpdateCmdDataSource = oComm
            This.DeleteCmdDataSource = oComm
            This.RefreshCmdDataSource = oComm
            *** Setup code: DO NOT REMOVE
            if This.__VFPSetup = 1
                This.__VFPSetup = 2
            ENDIF
            return llReturn
            *** End of Setup code: DO NOT REMOVE
        ENDPROC
        PROCEDURE AutoOpen
            *** Setup code: DO NOT REMOVE
            if not pemstatus(This, '__VFPSetup', 5)
                This.AddProperty('__VFPSetup', 1)
                This.Init()
            endif
            *** End of Setup code: DO NOT REMOVE
        ENDPROC
    ENDDEFINE

    Ich benutze die Klasse so:

      LOCAL oCA As catest
      oCA=Createobject("catest")
      IF TYPE("oCA")<>"O" OR ISNULL(m.oCa)
        MESSAGEBOX("Object oCA nicht da!")
        RETURN
      ENDIF 
      oCA.CursorFill()
      IF !USED("cari")
        MESSAGEBOX('Cursor "cari" nicht da!')
        RETURN
      ENDIF
      SELECT cari
      BROWSE
      IF TABLEUPDATE(2,.F.)  
        WAIT WINDOW "nach TABLEUPDATE"
      ELSE  
        AERROR(laErrors)
        MESSAGEBOX(laErrors[2])
      ENDIF
      USE IN cari    

    Jetzt bin ich wirklich ratlos. Kannst Du mal meinen mit Deinem Klassencode vergleichen?

    Gruß,

    Winfried

    Montag, 21. Januar 2013 13:15
  • Ich kann Dir meine Klasse um vergleich per viewcode.prg angeben:

    **************************************************
    *-- Class:        myca (d:\sandbox\astackinfo\libs\mycas.vcx)
    *-- ParentClass:  cursoradapter
    *-- BaseClass:    cursoradapter
    *-- Time Stamp:   01/21/13 01:55:12 PM
    *
    DEFINE CLASS myca AS cursoradapter


        Tag = "Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=D:\temp\data2.sdf;"
        Height = 22
        Width = 23
        SelectCmd = "select text, id from customer"
        CursorSchema = "TEXT C(100), ID C(38)"
        Alias = "cursdf"
        BufferModeOverride = 5
        DataSourceType = "ADO"
        Flags = 0
        CompareMemo = .F.
        WhereType = 1
        KeyFieldList = "ID"
        Tables = "customer"
        UpdatableFieldList = "TEXT"
        UpdateNameList = "TEXT customer.text, ID customer.id"
        UseTransactions = .F.
        Name = "myca"


        PROCEDURE Init
            *** Setup code: DO NOT REMOVE
            local llReturn
            do case
                case not pemstatus(This, '__VFPSetup', 5)
                    This.AddProperty('__VFPSetup', 0)
                case This.__VFPSetup = 1
                    This.__VFPSetup = 2
                case This.__VFPSetup = 2
                    This.__VFPSetup = 0
                    return
            endcase
            set multilocks on
            llReturn = dodefault()
            *** End of Setup code: DO NOT REMOVE
            *** Select connection code: DO NOT REMOVE

            local loConnDataSource
            loConnDataSource = createobject('ADODB.Connection')
            ***<DataSource>
            loConnDataSource.ConnectionString = [Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=D:\temp\data2.sdf;]
            ***</DataSource>
            loConnDataSource.Open()
            This.DataSource = createobject('ADODB.RecordSet')
            This.DataSource.CursorLocation   = 3  && adUseClient
            This.DataSource.LockType         = 3  && adLockOptimistic
            This.DataSource.ActiveConnection = loConnDataSource
            *** End of Select connection code: DO NOT REMOVE

            Local oComm
            oComm = Createobject("ADODB.Command")
            oComm.ActiveConnection = loConnDataSource

            This.InsertCmdDataSourceType = "ADO"
            This.UpdateCmdDataSourceType = "ADO"
            This.DeleteCmdDataSourceType = "ADO"
            This.RefreshCmdDataSourceType = "ADO"
                
            This.InsertCmdDataSource = oComm
            This.UpdateCmdDataSource = oComm
            This.DeleteCmdDataSource = oComm
            This.RefreshCmdDataSource = oComm

            *** Setup code: DO NOT REMOVE
            if This.__VFPSetup = 1
                This.__VFPSetup = 2
            endif
            return llReturn
            *** End of Setup code: DO NOT REMOVE
        ENDPROC


        PROCEDURE AutoOpen
            *** Setup code: DO NOT REMOVE
            if not pemstatus(This, '__VFPSetup', 5)
                This.AddProperty('__VFPSetup', 1)
                This.Init()
            endif
            *** End of Setup code: DO NOT REMOVE
        ENDPROC


    ENDDEFINE
    *
    *-- EndDefine: myca
    **************************************************

    Benutzung ist schon ok.

    Vielleicht hängts auch am Typ des Primärschlüssels?

    Meine Tabelle Customer hat als ID ein Feld vom Typ uniqueidentifier mit Standardwert newid() als Primary Key gesetzt.

    Vielleicht liegts an UseTransactions = .F., das sehe ich bei Dir nicht. Compact unterstützt zwar Transaktionen, aber nicht verschachtetlt und nicht verteilt. Versuch's mal ohne.

    Tschüß, Olaf.

    Montag, 21. Januar 2013 14:04
  • Hallo Olaf,

    ich hab auch mal die kleinen Änderungen gemacht, die meinen Ca noch von Deinem unterschieden: ohne Erfolg. Meine Tabelle hat auch Artikelnr als Primärschlüssel und ist damit Unique; die eingestellten Daten sind auch garantiert unique.

    Eigentlich bleibt nur noch ein Unterschied in den zugrunde liegenden Programmen und Libraries. Ich hab alle Updates zu VFP9SP2 installiert; die Runtime ist 09.00.0000.7423 vom 23.02.2009.

    Die Version der SQLCEOLEDB35.DLL ist 3.5.8980.0 vom 12.02.2010.

    loConnDataSource.Version (im CA.Init abgefragt) meldet 6.1.

    Mit SQL-Passthrough unter VFP funktioniert alles. Ebenso in VB.Net. Nur lesend funktioniert unter VFP der CA auch einwandfrei.

    Gruß,

    Winfried

    Version

    Montag, 21. Januar 2013 16:25
  • Muss ich mal schauen, ich kann Dir das erst Mittwoch sagen, morgen bin ich unterwegs.

    Tschüß, Olaf.

    Montag, 21. Januar 2013 20:04
  • Sowohl in system32 als auch SysWOW64 habe ich keine sqlceoledb35.dll, sondern sqloledb30.dll Version 3.0.7600.0

    Aktuell stürtzt mir VFP allerdings auch wieder ab.

    Genauer gesagt: Es funktionieren nur Inserts, neue Datensätze.

    Dementsptrechend habe ich jetzt mal

    "Update using" auf "SQL DELETE then INSERT" eingestellt, damit geht's. Das wird wohl der Knackpunkt sein. UpdateType = 2. WhereType immer noch 1.

    Das Keyfield muß dabei dann logischerweise auch mit in der UpdatableFieldList und UpdateNamelist eingefügt werden, sonst kriegt man im Fall von uniqueidentifier mit newid() Defaultwert eine neue GUID.

    In Deinem Fall mit dem Artikelnr C(18) Feld dürfte das kein Problem sein, bei int Identity wäre das ein Problem.

    Tschüß, Olaf.

    Mittwoch, 23. Januar 2013 10:44
  • Hallo Olaf,

    die SQLCEOLEDB35.DLL müsste bei Dir auch installiert sein; bei mir ist sie in C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v3.5\ und die ist auch registriert.

    Mit Insert/Delete werd ich das auch mal ausprobieren, aber es ist schade, dass das so unsicher läuft. Ich werde aber wohl auf die SQL passthrough-Befehle weiter zurückgreifen; die funktionieren ja.

    Allgemein ist der SqlServerCe nicht nur ein gutes Austauschformat mit Handheld-Terminals, sondern kann auch unter VFP alleine gute Dienste tun. Man kann z.B. damit ganz einfach seine Daten hochgradig verschlüsselt aufbewahren, beispielsweise für Backups, Datenaustausch mit anderen Rechnern oder um aufwendig recherchierte RO-Daten (bei mir z.B. eine komplette EDIFACT-Datenbank mit sämtlichen Specs seit 1993) vor Klau zu schützen.

    Gruß,

    Winfried

    Mittwoch, 23. Januar 2013 11:24
  • OK, da hatte ich nicht gesucht. Da hast Du trtozdem auch eine neuere Version als ich. Meine DLL ist auch vom 12.02, aber 3.5.8080.0

    Instabil würde ich das so noch nicht direkt nennen, evtl. muß man dem Adodb.command für funktionierende UPDATEs noch etwas mitgeben oder auch etwas in UpdateCmd eintragen, statt dem automatisch generierten SQL zu vertrauen.

    Evtl. ist dabei auch nicht das Update, sondern der Refresh das Problem und man muß die RefreshCmdDatasource weglassen. Es gäbe noch viel foxproseitig auszuprobieren und zu debuggen, z.B. könnte man bei BeforeUpdate, AfterUpdate, BeforeREcordRefresh mal einen Kommentar mit Breakpoint oder ein SET STEP ON reinsetzen und sehen, was da als Parameter reinkommt und ob das automatisch generierte Update-SQL auch Sinn macht.

    Tschüß, Olaf.

    Mittwoch, 23. Januar 2013 11:52