Fragensteller
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
- Bearbeitet Olaf Doschke Mittwoch, 16. Januar 2013 22:52
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.
- Bearbeitet Olaf Doschke Donnerstag, 17. Januar 2013 10:59
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.
- Bearbeitet Olaf Doschke Freitag, 18. Januar 2013 08:12
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 -
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.
- Bearbeitet Olaf Doschke Mittwoch, 23. Januar 2013 10:50
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