Page 1 of 2 1 2 LastLast
Results 1 to 15 of 21

Thread: Hoe kan ik de Primary Key wijzigen op de Client dataset.

  1. #1
    Christophe
    Join Date
    Jan 2004
    Location
    Belgium, West-Vlaanderen, Nieuwkerke
    Posts
    459

    Hoe kan ik de Primary Key wijzigen op de Client dataset.

    Dag allen,

    Momenteel ben ik bezig met een project waarvan we een Client/Server hebben.
    Voor de Server gebruiken we de datasnap (TDataSnapServer). Via bepaalde methodes kunnen we gegeven opvragen.
    De gegevens uit de database worden opgehaald via TFDConnection/TFDQuery. Die worden dan doorgestuurd als TFDJSONDataSets naar de Client.

    Via Client Gui wordt de TFDJSONDataSets via de TFDJSONDataSetsReader in de een TFDMemTable geplaatst.
    Tot hiertoe lukt het op gegevens om te halen vanuit de Client Gui.

    Het volgende methode is een insert van mijn data.
    Via de Client Gui wordt er een insert uitgevoerd. De TFDMemTable staat in cachedupdates.
    En mij PrimaryKey in de TFDMemTable staat op arAutoInc.
    Dus bij een insert staat mijn PK op een negatief getal. (bv -1)
    Bij een volgende insert is die PK van nieuw record op -2. (en zo verder)
    De data wil ik doorsturen naar de server. De data wordt opgevangen op de server en geplaatst in een TFDQuery.
    In de TFDQuery zijn de PK in record1 ook -1 en record2 op -2. Bij een ApplyUpdates worden de PK's gewijzigd volgens de PK uit de Database.

    Mijn vraag is nu hoe ik de PK's op de client -1 en -2 kan wijzigen?

    • een mogelijkheid is de hele selectie terug opvragen maar dan vind ik dit overkill als dit over een groot aantal records gaat en waarvan we 2 records hebben toegevoegd.
    • via de client enkel één per één record doorsturen en de PK terugkrijgen van de server. (200 insert = 200 insert calls naar server, ipv 1 delta insert call)
    • de delta insert doorsturen en een lijst met oude PK en nieuwe PK terug krijgen en deze dan updaten in de dataset. (aan de server kant de delta doorlopen en één per één inserten om zo de PK op te vangen)
    • andere oplossingen


    Zijn er mensen die hiermee ervaring hebben en mij in de juiste richting kan helpen?

  2. #2
    Reader
    Join Date
    May 2002
    Location
    Holland
    Posts
    3,382
    Na vele omzwervingen rond dit probleem besloot ik nooit meer met autoinc velden te werken maar met sequences (sql server) en de nieuwe PK altijd te clientside te genereren met een call naar de database "next value for" en expliciet te zetten in de dataaset.
    met autoinc velden bedoel ik dan autoinc in de database zelf. Wat er onder water gebeurt in FireDAC vind ik daar vrij ondoorzichtig.
    Voor andere databases zullen gelijksoortige calls mogelijk zijn: de generator voor FireBird bijvoorbeeld.

  3. #3
    Christophe
    Join Date
    Jan 2004
    Location
    Belgium, West-Vlaanderen, Nieuwkerke
    Posts
    459
    In deze project gebruiken we FireBird 3.0 en de PK is een Identity.

    Als ik het goed begrijp maken jullie een call naar de server en de server doet een insert zodat je de juist PK krijgt.
    Dan wilt dit zeggen dat je bij ieder insert/post aan de client een call doet naar de server?

  4. #4
    Reader
    Join Date
    May 2002
    Location
    Holland
    Posts
    3,382
    Ja, bij iedere insert van een dataset (AfterInsert of OnNewRecord event) vraag ik aan de DB de nieuwe waarde van de sequence.

    Er zijn misschien veel slimmere mensen dan ik, maar op deze manier werken "garandeert" me volledige controle.

    Het is een tijdje geleden dat ik met FIreBird werkte, en met 3.0 heb ik nog helemaal niet gewerkt.
    Wat ik in ieder geval nooit meer in mijn databases doe is dat de database ZELF de identity aanmaakt. In dat geval ben je namelijk altijd te laat aan de clientkant omdat de ID waarde pas bekend is nadat de data in de database zit.
    Wat tot allerlei problemen kan leiden in de BeforePost event. Ook gedoe met required fields, die je dan vaak in delphi op niet-required moet zetten. Iets dat natuurlijk eigenlijk zeer vreemd is :-) Valsheid in geschrifte.

  5. #5
    We doen het in ons framework anders. We gebruiken Oracle met sequences en in bijna alle gevallen wordt die sequence in een trigger gebruikt om de PK automatisch te zetten.

    Vervolgens kan de client, indien nodig, de sequence aanroepen en vragen welke waarde het laatst ingevoegd is. Dat hoeft dan ook alleen als dat relevant is.
    In andere gevallen zullen we toch meer data ophalen dan strict noodzakelijk. Bijvoorbeeld, bij het invoegen van een order halen we het orderid op via de sequence, maar bij het invoegen van een orderregel verversen we het grid met regels (dat ook op het orderscherm staat) in z'n geheel. In de praktijk zijn er meestal toch niet zoveel details en als dat er meer dan een paarhonderd worden, moet je je af gaan vragen of je UI wel goed is.

    Overigens weet ik niet helemaal waarom we die sequence gebruiken. Wellicht heeft het te maken met de oorspronklijke driver, of met de mogelijkheden van TClientDataSet. Want er is natuurlijk ook de returning into clause waarmee je velden (waaronder de PK) van het ingevoegde record kan terugkrijgen van het insert statement.

    Al met al is dat allemaal niet per se heel handig, en als ik het op dit moment allemaal over kon doen, dan zou ik er waarschijnlijk ook voor kiezen om de PK niet via de trigger te zetten, maar zelf te genereren (al dan niet m.b.v. de database).
    1+1=b

  6. #6
    Quote Originally Posted by GolezTrol View Post
    Vervolgens kan de client, indien nodig, de sequence aanroepen en vragen welke waarde het laatst ingevoegd is. Dat hoeft dan ook alleen als dat relevant is.
    Ik neem aan dat dat niet gebeurd voor het toevoegen van detail regels? Want dan krijg je met "laatst ingevoegde" ID in een multi-user omgeving soms de verkeerde terug. Vandaar dat voor het toevoegen van detail-regels vaak de GEN_ID() of "next value for" gebruikt wordt voordat de master toegevoegd wordt.

    In geval van Identity is dat volgens mij niet mogelijk (omdat de generator/sequence "intern" is) en zul je wel met de RETURNING INTO clause moeten werken om de ID terug te krijgen en die te gebruiken voor de detail-regels.

  7. #7
    Reader
    Join Date
    May 2002
    Location
    Holland
    Posts
    3,382
    Precies !
    Identity later terugvragen is niet 100% veilig. RETURNING wel.

  8. #8
    Marius
    Join Date
    Jul 2013
    Location
    Groningen
    Posts
    176
    Wij reserveren soms tientallen id's dmv de gen_id wat aanzienlijk wat database query tijd scheelt (ook het opvragen van 1 id kost je een round trip naar de database). Dat doen we voor mssql, firebird en mysql (en daardoor kun je zoals Eric al aangeeft je dependencies direct vullen). Werkt uitstekend in combinatie met een OPF systeem

  9. #9
    Quote Originally Posted by rvk View Post
    Want dan krijg je met "laatst ingevoegde" ID in een multi-user omgeving soms de verkeerde terug.
    Nee, dat is niet waar. In Oracle tenminste, maar ik denk ook in de andere databases (behalve MySQL, want MySQL). Die 'current value' van een sequence is sessiegebonden. Ik krijg dus het laatste id dat ik heb gegenereerd. En als ik probeer om het op te vragen in een sessie waarin ik geen id heb gegenereerd, dan krijg ik zelfs een foutmelding.
    1+1=b

  10. #10
    Quote Originally Posted by GolezTrol View Post
    Nee, dat is niet waar. In Oracle tenminste, maar ik denk ook in de andere databases (behalve MySQL, want MySQL). Die 'current value' van een sequence is sessiegebonden. Ik krijg dus het laatste id dat ik heb gegenereerd. En als ik probeer om het op te vragen in een sessie waarin ik geen id heb gegenereerd, dan krijg ik zelfs een foutmelding.
    Ok. Nee, dat is niet mogelijk in Firebird. Daar staat de SEQUENCE/GENERATOR buiten de transacties. En die kent geen LAST_ID() functie. Dus is het daarbij niet mogelijk om de laatste toegevoegde sequence op te halen binnen die transactie. Vandaar dat je in Firebird altijd eerst een GEN_ID() moet doen (met ophoging van de sequence) en dan de master en detail-records daarop toe kunt voegen. En bij Identity moet je dus wel met RETURNING werken want ik denk niet dat er in Firebird 3.0 iets toegevoegd is dat deze waarde binnen de transactie op kan halen.

  11. #11
    Ah, ik dacht serieus dat dat in FireBird ook zo werkte, maar dat blijkt inderdaad niet te kunnen. Oeps.

    Leerzaam threadje overigens! Zo leer ik net dat GET_ID deprecated is. Tegenwoordig doe je blijkbaar bij voorkeur NEXT VALUE FOR.
    1+1=b

  12. #12
    Senior Member
    Join Date
    Jul 2008
    Location
    Ertvelde, Belgi?½
    Posts
    158
    Even een kleine update misschien want stiekem werken Chrostophe en ikzelf aan hetzelfde project.

    We gebruiken dus FireDAC als DB Access Componenten naar FireBird toe. We gebruiken dus ook niet de oude ClientDataSet / DataSetProvider DataSnap approach, maar de FireDAC DataSet approach. Wat we willen bereiken is na een insert een record met al zijn velden opnieuw ophalen, maar dat kan dus eigenlijk enkel als we ook de nieuwe ID van dat record weten. Niet alleen willen we de nieuwe ID weten, maar bij een UPDATE willen we eventueel ook de nieuwe waarden van bepaalde velden weten die server site werden opgevuld (bv zeg ik maar iets UpdatedBy en UpdateOn).

    De Client gebruikt dus TFDMemTAble componenten die met een call naar de DataSnap server dus worden opgevuld met data die serverside via een TFDQuery worden opgehaald. Updates in de TFDMemTable client side worden dus met een method terug naar de Server gestuurd, waar die dan via een TFDJSONDeltas.ApplyUpdates eigenlijk perfect gersolved worden. FireDAC maakt de correcte SQL Statements voor INSERT / UPDATE / DELETE, ... Indien nodig kunnen we ook aan de serveerkant de TFDJSONDeltas filteren om enkel de inserts apart te processen en via een INSERT INTO ... RETURNING ... SQL Statement de INSERT te doen en de ID terug te krijgen.

    Ons probleem is nu dat the TFDMemTable aan de client precies geen weet heeft van wat er allemaal aan de server gebeurt en dus die RETURNING ID ook niet terug krijgt.

    Onze voorlopige oplossing is nu een InDeltas en OutDeltas in onze server side Update methode. Server side filteren we dan eerst de InDeltas op [ rtInserted ] zodat we de nieuw toegevoegde records apart kunnen processen. We lopen even manueel door de records in die gefilterde dataset en doen de INSERT INTO ... RETURNING ... manueel. Aangezien we server side dan wel de nieuwe waarde voor de ID's hebben gooien we de dataset met de nieuwe ID's als bijkomend veld in de OutDeltas die we dan terug client side kunnen processen. Het klinkt een beetje omslachtig, maar het werkt wel. Een demo app is eventueel verkrijgbaar mochten jullie dat wensen.

    Goed ... nu moeten we werken aan het volgende probleem. Het is immers zo dat een .RefreshRecord op een TFDMemTable client side ook niets doet, en we dus blijkbaar daarvoor ook een eigen method zullen moeten voorzien en het geheel manueel processen. Maar we geraken er wel.

    Ik moet nu wel zeggen dat wij vroeger ook zelf een table hadden met waarden voor PK velden, en we dus zelf via een StoredProcedure een nieuwe ID waarde genereerden. Die Stored Procedure konden we dan uitvoeren voor de effectieve insert op de DB gebeurde en dan hadden we inderdaad de nieuwe ID waarde nog voor de insert werd uitgevoerd. Enig nadeel is zo misschien wel dat je mogelijk met 'gaten' in je ID reeks zit.

    Voor wat betreft de FireDAC DataSnap manier van werken, valt het ons wel op dat er niet zo heel veel duidelijke voorbeeld zijn. De voorbeelden die er dan wel zijn gaan meestal niet verder dan een simpele master / detail zonder UPDATE en al zeker geen INSERT. Een gemiste kans eigenlijk.

    Mochten jullie op dit vlak nog verder tips / vragen / suggesties hebben, we horen het graag!

    Met vriendelijke groeten,


    Stefaan

  13. #13
    Quote Originally Posted by Stefaan Lesage View Post
    Enig nadeel is zo misschien wel dat je mogelijk met 'gaten' in je ID reeks zit.
    Dat is geen nadeel om je zorgen over te maken. Dat gedrag zien we ook in Oracle. De sequences daar leven buiten de transactie. Als je een nieuw ID opvraagt, dan is dat gebruikt, zelfs als je de transactie terugrolt. En omdat we een cluster hebben, zullen de sequences zelfs bereiken van (meestal) 1000 reserveren. Dat resulteert in de mogelijkheid dat IDs niet oplopend zijn, of dat er (na een update van een van die nodes) ineens een blok van een paarhonderd weg is.

    Al met al moet dat geen probleem zijn. Het gaat erom dat elke rij een uniek nummer heeft, maar verder moet je geen waarde willen hangen aan de precieze waarde of zelfs de volgordelijkheid.
    1+1=b

  14. #14
    Senior Member
    Join Date
    Jul 2008
    Location
    Ertvelde, Belgi?½
    Posts
    158
    Quote Originally Posted by GolezTrol View Post
    Al met al moet dat geen probleem zijn. Het gaat erom dat elke rij een uniek nummer heeft, maar verder moet je geen waarde willen hangen aan de precieze waarde of zelfs de volgordelijkheid.
    Klopt eigenlijk ... Voor bepaalde records (data m.b.t. boekhouding) is een oplopend nummer zonder gaten wel vereist, maar dat doen we dan wel in een veld naast de Primary Key IMHO. Ik probeer nu nog even een en andere om alles misschien wat eenvoudige te laten lopen, maar we hebben een werkend prototype.

  15. #15
    Senior Member
    Join Date
    Dec 2003
    Location
    Den Haag
    Posts
    205
    TFDMemTable heeft een property UpdateOptions. Daar kan je bijvoorbeeld AutoIncFields specificeren, als ook KeyFields. Gebruiken jullie deze? Of alleen aan de server kant? Let wel op om bij FDConnection de ExtendedMetaData property op True te zetten. Hebben jullie nu client side een FDConnection component met bij DriverName property de waarde DS? Zoveel properties, zoveel mogelijkheden. Probeer anders eens onder UpdateOptions een GeneratorName te specificeren. FireBird ondersteunt generators.

Page 1 of 2 1 2 LastLast

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •