Alla inlägg

Flytta Dataverse-data med Shuffle: en praktisk guide

Publicerad

En praktisk guide till att flytta data med Shuffle

Vad Shuffle gör, och vad som är nytt

Shuffle flyttar Dataverse-data och lösningar mellan miljöer utifrån en enda XML-definition. 2026 års version gör verktyget snabbare, komplett och säkrare. Det är den första versionen av XrmToolBox-verktygen sedan maj 2023, och samma motor finns i byggstegen för Azure DevOps.

Jonas Rapp beskrev verktygens bakgrund i sin DevOps-serie, och Sara Lagerquist visade ett komplett exempel i sitt adventskalenderinlägg. Båda är fortfarande värda att läsa. Den här guiden tar vid där: hur du använder verktygen i dag, och hur du använder det som är nytt.

I guiden:

  • Skriva en definition som exporterar och importerar referensdata
  • Köra den i XrmToolBox och läsa loggen
  • Slå på batchning, och när du inte ska göra det
  • Vad som har ändrats i beteendet, så att uppgraderingen inte bjuder på överraskningar
  • Köra samma definition i en pipeline

Skaffa verktygen

Installera Shuffle från XrmToolBox Tool Library. Installerar du ett verktyg följer alla tre med.

Verktyg Paket i Tool Library Vad du använder det till
Shuffle Builder Rappen.XrmToolBox.Shuffle.Builder Skriva och redigera definitionsfiler i en visuell editor
Shuffle Runner Rappen.XrmToolBox.Shuffle.Runner Köra en definition: exportera till fil eller importera en fil
Shuffle Deployer Rappen.XrmToolBox.Shuffle.Deployer Driftsätta paketerade definitioner (.cdpkg) modul för modul

För pipelines lägger tillägget för Azure DevOps till stegen Shuffle Export och Shuffle Import, som kör samma definitioner. Mer om dem i slutet.

En definition på fem minuter

En definition är en lista med block som körs i ordning. Varje datablock anger vilken tabell som ska exporteras och hur den ska importeras. Den här flyttar länder och sedan deras städer, och matchar på namn så att målmiljön behåller sina egna id:n:

<ShuffleDefinition StopOnError="false">
  <Blocks>
    <DataBlock Name="Countries" Entity="cint_country">
      <Export>
        <Attributes>
          <Attribute Name="cint_name" />
          <Attribute Name="cint_code" />
        </Attributes>
      </Export>
      <Import Save="CreateUpdate">
        <Match>
          <Attribute Name="cint_name" />
        </Match>
      </Import>
    </DataBlock>
    <DataBlock Name="Cities" Entity="cint_city">
      <Export>
        <Attributes>
          <Attribute Name="cint_name" />
          <Attribute Name="cint_country_id" />
        </Attributes>
      </Export>
      <Import Save="CreateUpdate">
        <Match>
          <Attribute Name="cint_name" />
          <Attribute Name="cint_country_id" />
        </Match>
      </Import>
    </DataBlock>
  </Blocks>
</ShuffleDefinition>

Vad delarna gör:

  • Export listar kolumnerna som ska med. En avslutande * fungerar också, till exempel cint_*.
  • Import Save avgör vad som händer per post: CreateUpdate (standard), CreateOnly, UpdateOnly eller Never.
  • Match anger kolumnerna som identifierar en befintlig post i målmiljön. Utan den skapas varje post.
  • Uppslag följer med automatiskt. Städer pekar på länder via id, och Shuffle mappar varje käll-id till det id som landet fick i målmiljön.
  • Blockens ordning spelar roll. Importera föräldrar före de poster som slår upp dem.

Builder skriver allt detta åt dig, och panelen Information förklarar varje nod.

Exportera med Runner

I Runner väljer du Export, definitionen och en datafil att skriva, väljer serialiseringsformat och kör. Varje export är nu komplett. Tidigare versioner stannade vid 5 000 poster per block utan varning, så en tabell med 7 500 poster blev tyst 5 000.

Format Skriver Använd när
SimpleWithValue XML med värden, och namn för uppslag Standard; läsbara diffar i versionshanteringen
Simple XML med enbart värden Mindre filer, samma importresultat
SimpleNoId Simple utan post-id:n Målmiljön ska ge posterna nya id:n
Explicit XML med tabell- och kolumnnamn som elementnamn Läsa filen för hand
Text Avgränsad text med en rubrikrad per block Filer som ska öppnas i kalkylark
Full .NET-serialisering av posterna Exakta rundresor, inte för läsning

För att välja poster lägger du till elementen <Filter> och <Sort>, ActiveOnly="true", eller en <Relation> som begränsar ett block till poster som hör till ett annat blocks poster. För mer än så skriver du en <FetchXML>-fråga. Den bläddrar också förbi 5 000 poster.

Kolumner skrivs i bokstavsordning, och tal och datum i ett fast format (1234.5, 2026-10-05T12:30:00.0000000Z). Att exportera oförändrad data igen ger ingen diff, oavsett vilken dator som kör.

Importera, och läsa loggen

För att importera väljer du Import i Runner, definitionen och datafilen, och kör mot målanslutningen. För varje post hittar Shuffle målposten via Match och skapar, uppdaterar, hoppar över eller misslyckas sedan med den.

De inställningar du oftast använder:

  • CreateWithId="true" skapar poster med deras käll-id. Användbart för konfigurationsdata som andra system eller lösningar refererar till via id.
  • UpdateIdentical avgör om en matchad post som inte har ändrats skrivs ändå. Låt den vara avslagen så hoppas identiska poster över, vilket går snabbare och håller granskningshistoriken ren.
  • <Match PreRetrieveAll="true"> läser måltabellen en gång i början av blocket och matchar i minnet, i stället för en fråga per post. Den läser nu alla rader, inte bara de första 5 000.
  • Delete="Existing" ersätter en matchad post (radera, sedan skapa). Delete="All" utan Match tömmer måltabellen först. Båda är till för data som du äger helt.

Runner skriver en logg bredvid definitionen, namngiven efter definition, åtgärd, anslutning och tid. Varje block avslutas med en sammanfattningsrad, och det är den du ska titta på först:

Importing block Cities - 12000 records
Pre-retrieved 12000 records for matching (12000 distinct match keys, 2800 ms)
Created: 0 Updated: 0 Skipped: 12000 Deleted: 0 Failed: 0

Skapade, uppdaterade, överhoppade och misslyckade blir alltid tillsammans blockets antal poster. En misslyckad post loggas med radnummer, etikett och serverns meddelande, så att du hittar den i datafilen.

Snabbare importer

Batchning är något du aktivt slår på. Sätt BatchSize på ett blocks Import, så skickar Shuffle poster i grupper i stället för en begäran per post. En definition som inte sätter den importerar precis som tidigare.

<Import Save="CreateUpdate" BatchSize="100">
  <Match PreRetrieveAll="true">
    <Attribute Name="cint_name" />
  </Match>
</Import>

Shuffle kontrollerar per tabell vad miljön stöder. Den använder CreateMultiple/UpdateMultiple i Dataverse, ExecuteMultipleRequest on-premises, eller enskilda anrop där inget av dem finns. Loggen visar vad den hittade, till exempel CreateMultiple support for cint_city: True. Microsoft rekommenderar en batchstorlek på ungefär 100 för standardtabeller. I Builder är det fältet Batch size på noden Import.

Tre regler avgör om ett block faktiskt batchas:

  • Ett block med Match behöver PreRetrieveAll="true". Utan den måste varje posts matchningsfråga se de poster som skapats hittills, så batchen skickas före varje fråga och inget grupperas.
  • Poster med statecode, statuscode eller ownerid skickas en i taget, eftersom status och ägare kräver separata anrop. Lägg till DeferStateAndOwner="true" (kryssrutan Defer state and owner), så sparar Shuffle posterna i batchar först och sätter status och ägare i ett andra pass. Slutresultatet blir detsamma.
  • Upsert-vägen hoppar över matchningen helt och skickar UpsertMultiple. Den gäller bara när blocket matchar på enbart primärnyckeln, med CreateWithId="true", UpdateIdentical="true", Save="CreateUpdate", utan Delete och med BatchSize större än 1. Upsert hittar poster enbart via id, och därför går all annan matchning den vanliga vägen.

När du inte ska batcha:

  • Tabeller med tunga plug-ins. En batch är en transaktion, så en felaktig post fäller hela batchen. Plug-ins körs ändå för varje post. Batchning sparar rundresor, inte deras kostnad.
  • PreRetrieveAll på mycket stora eller tunga tabeller. Den håller hela måltabellen i minnet, med matchningskolumnerna och de importerade kolumnerna. Ett annotation-block som importerar documentbody skulle läsa varje bilaga i miljön.
  • Datafiler med dubbletter. PreRetrieveAll ser inte poster som skapats tidigare i samma block, så två poster med samma matchningsvärden skapas båda. Utan den skulle den andra uppdatera den första.

Hur skillnaden ser ut

Tänk dig två typiska jobb: att importera om en referenstabell med 12 000 städer till en miljö som redan har dem, och att läsa in en hel uppsättning konfigurationstabeller i en tom miljö. Siffrorna är illustrativa, men beteendet är det som versionen faktiskt har:

Jobb Före Efter
Importera om 12 000 städer med PreRetrieveAll Bara de första 5 000 målraderna läses; övriga 7 000 poster ser nya ut och misslyckas som dubbletter Alla 12 000 rader läses på några sekunder; varje post matchas och hoppas över som identisk
Samma jobb i XrmToolBox Importen väntar på att Runner-fönstret ritas om efter varje post, vilket lägger till minuter på ett stort block Fönstret håller inte längre uppe importen
Konfigurationstabeller till en tom miljö, batchat Varje post skapas med ett eget anrop Poster skapas i batchar om 100 med CreateMultiple

En lärdom värd att spara: när en import går långsamt, titta på tiden per post i loggen innan du skyller på Shuffle. Om en tabell tar 200 ms per post medan en annan tar 20 ms i samma körning ligger skillnaden oftast på serversidan, till exempel en synkron plug-in som körs vid varje skapande. Batchning sparar rundresor. Den kan inte göra serverns eget arbete billigare.

Vad som har ändrats, så att uppgraderingen inte överraskar

Befintliga definitioner fungerar som förut. Utan BatchSize batchas inget, och inget annat nytt alternativ är påslaget som standard. Några saker beter sig annorlunda, alla med avsikt:

Ändring Före Nu
En post vars uppdatering servern avvisar Räknades som Skipped, och körningen fortsatte även med StopOnError Räknas som Failed; StopOnError stoppar körningen
PreRetrieveAll på stora tabeller Läste de första 5 000 raderna Läser hela tabellen, sida för sida
Export av stora tabeller Avbröts vid 5 000 poster per block Komplett
Tal i datafiler Skrevs i datorns format, så 1234,5 på en dator kunde bli 12345 på en annan Skrivs alltid som 1234.5
Flyttalskolumner Importerades bara från formatet Full Importeras från alla format
En matchningskolumn som finns men är tom Fällde posten Jämförs som tom

Två saker att kontrollera i dina egna datafiler:

  • Decimalkomma. En äldre fil med 1234,5 importeras fortfarande på en dator som använder decimalkomma. På alla andra avvisas värdet nu med ett fel i stället för att läsas fel. Exportera om filen en gång så är det åtgärdat för gott.
  • Datum. Nya exporter skriver datum som 2026-10-05T12:30:00.0000000Z. De läses precis som tidigare, så filer med datum från kolumner som bara har datum eller är tidszonsoberoende importeras som de alltid har gjort.

Kör samma definitioner i en pipeline

En definition som du har testat i Runner körs oförändrad i Azure DevOps med steget Shuffle Import (och Shuffle Export åt andra hållet). Lägg definitioner och datafiler i versionshanteringen bredvid dina lösningar, och importera dem efter att lösningen har driftsatts:

- task: ShuffleImport@9
  displayName: Import reference data
  inputs:
    crmConnectionString: $(TargetConnectionString)
    definitionFile: $(Build.SourcesDirectory)/data/ShuffleReferenceData.xml
    dataFile: $(Build.SourcesDirectory)/data/ShuffleReferenceData.data.xml

Spara anslutningssträngen som en hemlig pipelinevariabel. Pipelines låser huvudversionen (@9), så de får den nya versionen utan någon ändring.

Två vanor som lönar sig: testa en batchad definition i Runner mot en kopia av målmiljön först, och läs blocksammanfattningarna i pipelineloggen på samma sätt som i XrmToolBox.

Sammanfattning och länkar

Uppgradera, behåll dina definitioner som de är och slå på batchning block för block där det lönar sig. Börja med stora referenstabeller utan tunga plug-ins: lägg till PreRetrieveAll="true" på deras Match, sätt BatchSize="100" och jämför blocksammanfattningarna före och efter.