1s 8 transaktioner av externa källor. Engångskunder. Segmentering för att få återkommande köp. Kostnaden för varor i beställningen

Oavsett det valda operationsalternativet (fil eller klient-server) tillhandahåller 1C:Enterprise-systemet arbete med information som lagras i databasen med hjälp av transaktionsmekanismen.

transaktion- Detta är en odelbar sekvens av datamanipuleringsoperationer när det gäller inverkan på databasen. Det är allt-eller-inget och tar databasen från ett konsekvent tillstånd till ett annat konsekvent tillstånd. Om, av någon anledning, en av transaktionens åtgärder misslyckas eller någon form av systemfel uppstår, återgår databasen till det tillstånd som var innan transaktionens start (transaktionen rullas tillbaka).

1C:Enterprise-systemet anropar implicit transaktioner när de utför åtgärder relaterade till ändring av information som lagras i databasen. Till exempel anropas alla händelsehanterare som finns i objekt- och postuppsättningsmodulerna som är associerade med uppdatering av databasdata i en transaktion. Transaktionen läser också objekt av följande typer: Exchange PlanObject, DocumentObject, DictionaryObject, Plan Of Types Of CharacteristicsObject, Plan Of Types of CalculationObject, Chart Of AccountsObject, BusinessProcessObject, TaskObject, SequenceRecordSet,RegisterInformationRecordSet, AccumulationRegisterRecordRecordingSet,RecordingSet. Samtidigt, i det hanterade låsläget, sätts ett delat lås på registrarens värde för postuppsättningar och på filtervärdena för postuppsättningen för ett oberoende informationsregister.

Tillsammans med detta kan utvecklaren använda arbetet med transaktioner i en explicit form. För detta används de globala sammanhangsprocedurerna. StartTransaction(), CommitTransaction() och CancelTransaction().

Använda explicit transaktionsanrop

Metod StartTransaction() låter dig öppna en transaktion. Därefter kan alla ändringar av databasinformationen som utförs av efterföljande uttalanden antingen accepteras i sin helhet eller avvisas i sin helhet. För att acceptera alla ändringar som görs, använd metoden CommitTransaction(). För att ångra alla ändringar som gjorts i en öppen transaktion, använd metoden CancelTransaction(). Om antalet metod anropar StartTransaction()överstiger antalet metodanrop CommitTransaction() eller CancelTransaction(), då kommer systemet att göra ett implicit metodanrop CancelTransaction() i följande fall:

● i slutet av den inbyggda språkexekveringen (händelsehanterare, extern anslutning, automationsserver);

● vid överföring av kontroll från servern till klienten.

Om antalet metod anropar CommitTransaction() eller CancelTransaction()överstiger antalet metodanrop StartTransaction(), sedan när ett extra metodanrop görs CommitTransaction() eller CancelTransaction() ett undantag kommer att kastas. Således kan schemat för att arbeta med en transaktion i allmänhet se ut så här:

Försök

StartTransaction();

// Ordföljd

CommitTransaction();

Undantag

CancelTransaction();

Slut på försök;

När du använder detta schema, tänk på att inte alla fel som uppstår när du arbetar med en databas hanteras på samma sätt av systemet. I allmänhet kan alla databasfel delas in i två kategorier:

● ej återvinningsbar,

● återvinningsbar.

Oåterställbara felär fel som kan störa 1C:Enterprise-systemets normala funktion, till exempel kan data vara korrupta. Om ett oåterställbart fel uppstår avslutas exekveringen av 1C:Enterprise-systemet i alla fall. Om ett oåterställbart fel inträffar under utförandet av en transaktion, avbryts alla ändringar som görs inom denna transaktion av systemet.

Återställningsbara felär fel som inte orsakar allvarliga störningar i driften av 1C:Enterprise-systemet. I händelse av ett återställningsbart fel kan ytterligare drift av systemet fortsätta. I det här fallet stannar naturligtvis själva operationen, som orsakade felet, och ett undantag kastas, som kan avlyssnas och bearbetas av konstruktionen

Försök... Undantag... Slut på försök.

Kapslade transaktionssamtal

Som en del av en redan pågående transaktion kan du komma åt procedurer StartTransaction(), CommitTransaction() och CancelTransaction(). Till exempel kan följande anropsmönster användas:

StartTransaction();

StartTransaction();

CommitTransaction();

// Kapslade transaktionssamtal

StartTransaction();

CommitTransaction();

CommitTransaction();

Ett sådant samtal innebär dock inte starten på en ny transaktion inom ramen för en redan pågående.

UPPMÄRKSAMHET!1C:Enterprise stöder inte kapslade transaktioner.Detta innebär att endast transaktionen av högsta nivån.

Alla transaktioner som anropas inom en redan öppen transaktion tillhör faktiskt samma transaktion och bildar inte en kapslad transaktion. Sålunda kommer att ångra ändringar som utförs i en kapslad transaktion slutligen inte resultera i att ändringarna i själva den kapslade transaktionen ångras, utan i att alla ändringar i transaktionen på översta nivån ångras. Samtidigt ignoreras commits gjorda i en kapslad transaktion.

Inverkan av transaktioner på driften av programobjekt

I allmänhet är mjukvaruobjekten som används av 1C:Enterprise-systemet absolut "transparenta" för databastransaktioner. Med andra ord kan databastransaktioner anropas när de körs olika metoder programobjekt, men till exempel, de åtgärder som utförs av databasen när en transaktion återställs påverkar i allmänhet inte motsvarande programvara objekt.

Det följer av detta att vid avbrytande av databastransaktioner måste utvecklaren (om nödvändigt) självständigt säkerställa en adekvat ändring av uppgifterna för motsvarande program objekt. Detta kan göras genom att läsa om alla objektets data, eller genom att ändra några av attributen för programobjektet.

Det finns undantag från denna regel. På grund av de betydande applikationsspecifikationerna för 1C:Enterprise-programobjekt kan i vissa fall återställningen av ändringar som gjorts i databasen fortfarande påverka värdena för egenskaperna för motsvarande program objekt. Detta händer i följande fall:

● när en transaktion avbryts, återställer flaggan för dokumentbokföring värdet den hade innan transaktionens början;

● om objektet skapades och skrevs i en transaktion, rensas värdet på länken när transaktionen återställs;

● om objektet skapades utanför en transaktion och en automatiskt genererad kod/nummer användes när det skrevs i transaktionen, raderas koden/numret när transaktionen avbryts.

2017-08-12

Skapa anpassade transaktioner för att underhålla OM-objekt.

Introduktion

Jag tror att många fungerande SAP-konsulter har stött på transaktionen att underhålla organisatoriska förvaltningsobjekt. Nämligen transaktionen PP01

Genom att använda denna transaktion kan användaren administrera informationstyper för organisationshantering för de objekttyper som används i automatiserade affärsprocesser. Mycket ofta används denna transaktion som en enda ingångspunkt för att arbeta med alla typer av organisatoriska förvaltningsobjekt, vilket i själva verket inte är en särskilt bra praxis. Tja, eller inte särskilt bekvämt. Fast visst vanligt. Därefter ska jag försöka berätta vad ett alternativ kan vara.

Tabell T77S0, grupp "TCODE"

När du konfigurerar OM-objektobjekt kommer du troligtvis att trycka på inställningen som finns i följande sökväg in SPRO:

IMG: Personal Management -> Organisation Management -> Basic Settings -> Data Model Enhancement -> Maintain Object Types

Här skapar du nya OM-objekt, ger dem namn, väljer ikoner och definierar några inställningar för dem... För tillfället är vi intresserade av noden " Objekttyp Nyckel + Transaktion"

En del av inställningsvyn öppnas framför dig. T77S0 med filtrerade gruppvärden

Det är värt att uppmärksamma gruppen TCODE där du, om du tittar noga, kan hitta de tekniska namnen på transaktioner som du med största sannolikhet var tvungen att arbeta med. Dessutom i kolumnen värde vilken typ av objekt som den eller den transaktionen är avsedd för anges.

Vad är speciellt med dessa transaktioner?

Genom att använda transaktioner som är utformade för att underhålla en specifik typ av objekt, behöver du inte välja just dessa typer av objekt som är tillgängliga som standard i en transaktion PP01. Det vill säga genom att köra till exempel en transaktion PO09, du börjar genast arbeta med objekt av typ L

Skapa en ny transaktion för ditt eget organisatoriska förvaltningsobjekt

I en av mina tidigare anteckningar talade jag om hur du kan skapa ett nytt OM-objekt + lägga till struktursökning för det

Jag kommer inte att avvika långt från detta material. Som en demonstration kommer jag att skapa en ny transaktion för att underhålla objektet 91.

Definiera en ny objekttyp i T77S0

Definiera namnet på den framtida transaktionen i inställningsvyn T77S0

Värdet "ZP91M" i detta fall är namnet på den framtida transaktionen för att underhålla objektet 91 . Spara dina ändringar.

Skapar en ny transaktion för att underhålla OM-objektet

Med en transaktion SE93 skapa en transaktion för att behålla ditt objekt. Nedan finns ett videoklipp med en sekvens av åtgärder som måste utföras för att skapa motsvarande transaktion

Var uppmärksam på de värden som användes för fälten program, skärmnummer,Auktoriseringsobjekt. Starta nu en ny transaktion

Användaren har möjlighet att bara arbeta med en viss typ av objekt, som i en viss mening kan kallas bekvämlighet, och, om du vill, minimering av ytterligare åtgärder för att välja det önskade objektet.

Förra gången vi recenserade enklaste sättet använder det inbyggda språket 1C. På praktik transaktioner mycket oftare används i samband med designen. Detta gör det möjligt att, i händelse av ett fel, fortsätta exekveringen av koden, samt utfärda ett adekvat felmeddelande till användaren och skriva information till loggen eller loggfilen för vidare analys av systemadministratören.

Om vi ​​vänder oss till den tekniska dokumentationen eller till ITS-disken kommer vi att se att 1C rekommenderar följande metod för att organisera en transaktion i ett försök

Försök //ett. Transaktionsstart. StartTransaction() ; //2. Ett block av operationer som utförs i en transaktion. //3. Om alla operationer är framgångsrika, genomför transaktionen. CommitTransaction() ; Undantag //fyra. Om fel uppstår när koden körs avbryter vi transaktionen. CancelTransaction() ; //5. Om det behövs, skriv till loggen. //6. Om det behövs, visa ett meddelande för användaren. EndTry ;

Faktum är att koden inte kräver några speciella förklaringar. Om i processen Försök exekvering av transaktionskoden, ett fel inträffar, faller vi omedelbart in i blocket undantag, dvs. före metoden CommitTransaction() vi kommer bara inte dit. Tja, i undantagsfall avbryter vi transaktionen i enlighet med detta och visar vid behov ett felmeddelande och skriver information till loggen. Det är mycket önskvärt att åtgärda fel i loggen, särskilt för de operationer som utförs utan användaringripande (till exempel schemalagda uppgifter). Detta kommer att möjliggöra ytterligare analys av felet. Istället för att skriva till loggen kan du ordna att meddelanden skickas till administratören via e-post.

Nu, beväpnade med ny kunskap, låt oss försöka modifiera koden som diskuteras i artikeln om . Låt mig påminna dig om att vi övervägde posten i katalogen Produkter och i informationsregistret Pris enligt följande schema:

&PåServerUtanKontext StartTransaction() ; //skriv nytt objekt Produkt = Kataloger. Produkter. CreateElement() ; Produkt. Namn = "Hålslag" ; Produkt. Write() ; //skriv pris Recordset =RegisterInfo. Pris. CreateRecordSet() ; NewRecord = Recordset. Lägg till() ; Ny ingång. Period = CurrentDate() ; Ny ingång. Produkt = Produkt. Länk; Ny ingång. Belopp = 100 ; Rekorduppsättning. Write() ; CommitTransaction() ; Slutprocedur

Låt oss nu lägga transaktionen i ett block Undantag för försök. Troligtvis kan fel uppstå endast i skrivande stund till katalogen eller till informationsregistret, därför förutbildning ta det ur transaktionen.

&PåServerUtanKontext Procedur ExecuteTransactionOnServer() //skapa en ny produkt Produkt = Kataloger. Produkter. CreateElement() ; Produkt. Namn = "Hålslag" ; //Skapa en post med ett pris Recordset =RegisterInfo. Pris. CreateRecordSet() ; NewRecord = Recordset. Lägg till() ; Ny ingång. Period = CurrentDate() ; Ny ingång. Belopp = 100 ; //Utför transaktionen i ett försök Försök att StartTransaction() ; Produkt. Write() ; Ny ingång. Produkt = Produkt. Länk; Rekorduppsättning. Write() ; CommitTransaction() ; Undantag CancelTransaction() ; Message = New MessageToUser; Meddelande. Text = ; Meddelande. Att rapportera() ; Loggboksinlägg( "Ett fel uppstod när produkten och dess pris registrerades"); EndTry ; Slutprocedur

Hur man INTE gör

De som precis har börjat arbeta med transaktioner har ofta en önskan om att göra det på detta sätt

StartTransaction() ; Försök att StartTransaction() ; //Block av operationer FixTransaction() ; Undantag CancelTransaction() ; EndTry ; Försök att StartTransaction() ; //Block av operationer FixTransaction() ; Undantag CancelTransaction() ; EndTry ; CommitTransaction() ;

Eller i en slinga

StartTransaction() ; För varje data från DataArray Loop Försök till StartTransaction() ; Data. Write() ; CommitTransaction() ; Undantag CancelTransaction() ; EndTry ; EndCycle ; CommitTransaction() ;

Vid första anblicken gjorde vi allt i enlighet med rekommendationerna från 1C. Men faktum är att 1C-plattformen inte stöder kapslade transaktioner. Det vill säga tekniskt sett kan man skriva så. Men samtidigt bildar alla kapslade transaktioner inte nya, utan hänvisar till samma transaktion på toppnivå. Således, om ett fel inträffar i en av de kapslade transaktionerna, kan nästa kapslade transaktion inte utföras. Systemet kommer att visa ett meddelande som: "Fel har redan inträffat i den här transaktionen!". Låt oss visa detta med ett exempel. Låt oss säga att vi bestämmer oss för att spela in två produkter, var och en i sin egen transaktion. Och låt oss göra dessa transaktioner kapslade i den tredje. Därefter kommer vi på konstgjord väg att orsaka ett fel i den första transaktionen med metoden ThrowException:

&PåServerUtanKontext Procedur ExecuteTransactionOnServer() StartTransaction() ; Försök att StartTransaction() ; Produkt = Kataloger. Produkter. CreateElement() ; Produkt. Namn = "Tabell" ; Produkt. Write() ; ThrowException "Produktinmatningsfel."; CommitTransaction() ; Undantag CancelTransaction() ; Message = New MessageToUser; Meddelande. Text = DescriptionErrors() Försök att starta en transaktion() ; Produkt = Kataloger. Produkter. CreateElement() ; Produkt. Namn = "Stol" ; Produkt. Write() ; CommitTransaction() ; Undantag CancelTransaction() ; Message = New MessageToUser; Meddelande. Text = DescriptionError() ; Meddelande. Att rapportera() ; EndTry ; CommitTransaction() ; Slutprocedur

Som ett resultat av denna procedur kommer vi att se följande i meddelandefönstret:

(ExternalProcessing.TransactionsTrying.Form.Form.Form(20)): Fel vid skrivning av objekt. (ExternalProcessing.TransactionInTry.Form.Form.Form(40)): Fel vid anrop av kontextmetod (Skriv): Fel har redan inträffat i denna transaktion!

Således är organisationen av kapslade transaktioner i 1C absolut meningslös.

Möjliga alternativ

Låt oss nu återgå till alternativet där vi spelade in produkten och priset för den. Om vi ​​har ett fel under genomförandet av transaktionen, kommer det att vara svårt att förstå vid vilken tidpunkt det inträffade - när du registrerar produkten eller när du registrerar priset, eftersom båda sker inom samma försök. För att avgöra var felet uppstod måste vi lägga in varje skrivoperation i sitt eget försök och undvika kapslade transaktioner. För att göra detta introducerar vi en boolesk variabel Vägran och beroende på dess värde vid slutet av alla operationer, kommer vi att begå eller avbryta transaktionen.

&PåServerUtanKontext Procedur ExecuteTransactionOnServer() // Starta transaktion Avvisa = Falskt ; StartTransaction() ; // Försöker skriva en produkt Prova Produkt = Kataloger. Produkter. CreateElement() ; Produkt. Namn = "Hålslag" ; Produkt. Write() ; Undantag Avvisande = Sant; Message = New MessageToUser; Meddelande. Text = "Fel vid skrivning av objekt"; Meddelande. Att rapportera() ; EndTry ; // Försöker skriva ner priset AttemptRecordset =RegisterInfo. Pris. CreateRecordSet() ; NewRecord = Recordset. Lägg till() ; Ny ingång. Period = CurrentDate() ; Ny ingång. Produkt = Produkt. Länk; Ny ingång. Belopp = 100 ; Rekorduppsättning. Write() ; Undantag Avvisande = Sant; Message = New MessageToUser; Meddelande. Text = "Fel vid skrivning av pris"; Meddelande. Att rapportera() ; EndTry ; // Besluta eller avbryta transaktionen Om INTE misslyckas då CommitTransaction() ; Else CancelTransaction() ; EndIf ; Slutprocedur

Du kan göra detsamma när vi itererar och skriver vilken data som helst i en loop. I det här fallet kan vi få en lista över alla data med eventuella fel.

Som förberedelse för 1C Expert-certifiering, på tröskeln till två mycket viktiga och globala ämnen - lås, skulle jag vilja analysera något utan vilket ovanstående begrepp är omöjliga - en DBMS-transaktion.

transaktion- en logiskt sammanhängande, odelbar sekvens av handlingar. Transaktionen kan antingen genomföras i sin helhet eller inte slutföras alls. COMMIT-metoden används för att utföra en transaktion i DBMS.

Ett typiskt exempel på en transaktion är en överföring Pengar från ett konto till ett annat:

  1. starta en transaktion;
  2. läs summan pengar på kontonummer 123;
  3. minska saldot på kontot 123 med 100 rubel;
  4. spara saldot på kontonummer 123;
  5. läs beloppet på kontonummer 321;
  6. öka saldot med 100 rubel;
  7. skriva ner ett nytt belopp på konto 321;
  8. genomföra transaktionen.

Få 267 1C-videolektioner gratis:

Som vi kan se, om transaktionen inte slutförs helt, är det inte vettigt.

Nyckelkrav (ACID) för ett transaktions-DBMS

En av de vanligaste uppsättningarna av krav för transaktioner och transaktions-DBMS är ACID-uppsättningen (Atomicity, Consistency, Isolation, Durability). Det här är egenskaperna som alla transaktioner bör ha:

  • Atomicitet- Ingen transaktion bör vara delvis fixerad.
  • Konsistens- Systemet är i ett konsekvent tillstånd före transaktionens början och måste förbli i ett konsekvent tillstånd efter att transaktionen har slutförts.
  • Isolering- Under genomförandet av en transaktion bör parallella transaktioner inte påverka dess resultat.
  • Varaktighet- i händelse av ett misslyckande bör ändringarna som gjorts av en framgångsrikt genomförd transaktion förbli sparade efter att systemet återgår till att fungera.

Transaktioner i 1C

Transaktioner i 1C 8.3 och 8.2 skapas både automatiskt och beskrivs av utvecklare.

Med metoden TransactionActive() kan du ta reda på om en transaktion är aktiv.

Ett exempel på en automatisk transaktion är behandlingen av att posta ett dokument, skriva ett ordbokselement till databasen, skriva en uppsättning informationsregisterposter, etc.

Titeln blev catchy, men kokade över. Jag måste säga direkt att vi kommer att prata om 1C. Kära 1C smeknamn, ni vet inte hur man arbetar med transaktioner och förstår inte vilka undantag som är. Jag kom till denna slutsats genom att titta på en stor mängd 1C-kod, född i det inhemska företagets vildmark. PÅ typiska konfigurationer detta är bra nog, men en fruktansvärd mängd anpassad kod skrivs inkompetent ur en databassynpunkt. Har du någonsin sett felet "Fel har redan inträffat i den här transaktionen"? Om så är fallet, gäller rubriken på artikeln dig. Låt oss slutligen ta reda på vad transaktioner är och hur man hanterar dem korrekt när man arbetar med 1C.

Varför du behöver slå larm

Till att börja med, låt oss ta reda på vad felet "Fel har redan inträffat i den här transaktionen" är. Detta är i själva verket en extremt enkel sak: du försöker arbeta med databasen i en redan återställd (avbruten) transaktion. Till exempel anropades CancelTransaction-metoden någonstans, och du försöker utföra den.


Varför är det dåligt? därför att givet fel säger ingenting om var problemet faktiskt inträffade. När en skärmdump med sådan text kommer till stöd från användaren, och speciellt för serverkod, som en person inte arbetar interaktivt med, är ... Jag ville skriva ett "kritiskt fel", men jag tänkte att detta är ett modeord som ingen längre uppmärksammar .... Det är en ass. Detta är ett programmeringsfel. Detta är inte ett slumpmässigt misslyckande. Detta är en bugg som måste göras om omedelbart. För när dina bakgrundsserverprocesser är uppe på natten och företaget börjar förlora pengar snabbt, då är "Fel har redan inträffat i den här transaktionen" det sista du vill se i diagnosloggarna.


Det finns naturligtvis en möjlighet att serverns tekniska logg (du har den aktiverad i produktionen, eller hur?) på något sätt kommer att hjälpa till att diagnostisera problemet, men just nu kan jag inte komma på ett alternativ direkt - exakt hur ska jag hitta den verkliga orsaken till det angivna felet i den. Och det finns bara en verklig anledning - programmeraren Vasya fick ett undantag i transaktionen och beslutade att en gång - inte en karabas "tänk, det är ett misstag, låt oss gå vidare."

Vad är transaktioner i 1C

Det är pinsamt att skriva om elementära sanningar, men lite måste tydligen göras. Transaktioner i 1C är samma som transaktioner i ett DBMS. Det här är inte några speciella "1C"-transaktioner, det är transaktioner i DBMS. Enligt den allmänna idén om transaktioner kan de antingen utföras helt eller inte alls. Alla ändringar i databastabeller som görs inom en transaktion kan ångras på en gång, som om ingenting hade hänt.


Vidare måste du förstå att kapslade transaktioner inte stöds i 1C. Faktum är att de inte stöds inte "i 1C", men de stöds inte alls. Åtminstone de där DBMS som 1C kan arbeta med. Kapslade transaktioner är till exempel inte tillgängliga i MS SQL och Postgres. Varje "kapslat" anrop till StartTransaction ökar helt enkelt transaktionsräknaren, och varje anrop till "CommitTransaction" minskar räknaren. Detta beteende beskrivs i många böcker och artiklar, men slutsatserna från detta beteende har tydligen inte analyserats tillräckligt. Strängt taget har SQL en sk SAVEPOINT, men 1C använder dem inte, och den här saken är ganska specifik.



Procedur Mycket användbar&ImportantCode(List of ReferenceReferences) StartTransaction(); För varje länk från listan över länkar i referensslingan Referensobjekt = Reference.GetObject(); ReferenceObject.WhichField = "Jag har ändrat från kod"; DirectoryObject.Write(); EndCycle; CommitTransaction(); Slutprocedur

Kod på engelska

Inte riktigt. Jag vill absolut inte duplicera exempel på engelska bara för att roa fans av holivar och heliga krig.


Visst skriver du sådan kod, eller hur? Kodexemplet som tillhandahålls innehåller fel. Minst tre. Vet du vad? Jag ska berätta om den första direkt, den är relaterad till objektlås och har inget direkt med transaktioner att göra. Om den andra - lite senare. Det tredje felet är ett dödläge som kommer att uppstå när den här koden exekveras parallellt, men detta är ett ämne för en separat artikel, vi kommer inte att överväga det nu för att inte komplicera koden. Nyckelord för att googla: dödläge hanterade lås.


Var uppmärksam, koden är enkel. Det här är bara en bil i dina 1C-system. Och den innehåller minst 3 fel samtidigt. Tänk i lugn och ro hur många fel det finns i mer komplexa scenarier för att arbeta med transaktioner skrivna av dina 1C-programmerare :)

Objektlås

Så det första misstaget. I 1C finns objektlås, de så kallade "optimistiska" och "pessimistiska". Vem som kom på termen, jag vet inte, skulle ha dödat :). Det är helt omöjligt att komma ihåg vem som är ansvarig för vad. Det är skrivet i detalj om dem och, liksom i annan allmän IT-litteratur.


Kärnan i problemet är att i det angivna kodexemplet ändras databasobjektet, men en interaktiv användare (eller en närliggande bakgrundstråd) kan sitta i en annan session, vilket också kommer att ändra detta objekt. Det är här någon av er kan få felet "posten har ändrats eller raderats". Om detta händer i en interaktiv session kommer användaren att klia sig i huvudet, svära och försöka öppna formuläret igen. Om detta händer i en bakgrundstråd måste du leta efter det i loggarna. Och registreringsloggen är som bekant långsam, och ELK-stacken för 1C-loggar i vår bransch sätts upp av ett fåtal ... (vi är förresten bland dem som ställer upp och hjälper andra att sätta upp: ))


Kort sagt, detta är ett irriterande misstag och det är bättre att inte ha det. Därför anger utvecklingsstandarderna tydligt att innan du byter objekt är det nödvändigt att sätta ett objektlås på dem med hjälp av " ReferenceObject.Lock()". Då kommer den samtidiga sessionen (som också bör göra det) inte att kunna starta ändringsoperationen och kommer att få det förväntade, kontrollerade felet.

Och nu om transaktioner

Med det första felet löst, låt oss gå vidare till det andra.


Om du inte tillhandahåller en undantagskontroll i den här metoden, kommer ett undantag (till exempel mycket troligt på metoden "Record ()" att kasta dig ut ur den här metoden utan att slutföra transaktionen. Ett undantag från "Write"-metoden kan göras av en mängd olika anledningar, till exempel kommer vissa applikationskontroller i affärslogiken att fungera, eller så kommer objektlåset som nämns ovan att inträffa. Hur som helst, det andra felet lyder: koden som startade transaktionen ansvarar inte för dess slutförande.



Det är så jag skulle kalla det här problemet. I vår 1C statiska kodanalysator baserad på SonarQube byggde vi till och med in sådan diagnostik separat. Nu arbetar jag på dess utveckling och fantasin om 1C-programmerare, vars kod kommer till mig för analys, ibland chockerar och förundrar mig ...


Varför? Eftersom ett undantag som slängs upp i en transaktion i 90 % av fallen kommer inte att tillåta denna transaktion att åtgärdas och kommer att leda till ett fel. Det bör förstås att 1C automatiskt rullar tillbaka en ofullständig transaktion först efter att ha återvänt från skriptkoden till plattformskodnivån. Så länge du är på 1C-kodnivån förblir transaktionen aktiv.


Låt oss gå upp en nivå i samtalsstacken:


Procedur ImportantCode() LinkList = GetWhereToLinkList(); Mycket användbar&ImportantCode(ReferenceList); Slutprocedur

Se vad som händer. Vår problematiska metod anropas någonstans utanför, högre upp i stacken. På nivån för denna metod har utvecklaren ingen aning om det kommer att finnas några transaktioner i metoden Very UsefulAnd ImportantCode eller inte. Och om de gör det, kommer de alla att bli färdiga... Vi är alla här för fred och inkapsling, eller hur? Författaren till metoden "ImportantCode" ska inte tänka på vad som exakt händer inuti metoden han kallar. Den där transaktionen behandlas felaktigt. Som ett resultat kommer ett försök att arbeta med databasen efter att ha kastat ett undantag från en transaktion, med hög sannolikhet, leda till det faktum att "I den här transaktionen, bla bla ..."

Smeta transaktioner över metoder

Andra regeln för "transaktionssäker" kod: transaktionsreferensantalet i början av metoden och i slutet måste ha samma värde. Du kan inte starta en transaktion med en metod och avsluta den med en annan. Du kan säkert hitta undantag från denna regel, men det kommer att vara någon slags lågnivåkod som är skriven av mer kompetenta personer. Generellt sett kan man inte skriva så här.


Till exempel:


Procedur ImportantCode() LinkList = GetWhereToLinkList(); Mycket användbar&ImportantCode(ReferenceList); CommitTransaction(); // En biljett till helvetet, ett seriöst samtal med författaren om våra svåra arbetsrelationer. Slutprocedur

Ovanstående är oacceptabel taskig kod. Du kan inte skriva metoder på ett sådant sätt att den som ringer kommer ihåg och håller reda på möjliga (eller troliga – vem vet) transaktioner inom andra metoder som den anropar. Detta är ett brott mot inkapsling och tillväxten av spagettikod som inte kan spåras samtidigt som förnuftet bibehålls.


Det är särskilt roligt att komma ihåg att den verkliga koden är mycket större än syntetiska exempel på 3 rader. Att söka efter påbörjande och avslutande transaktioner med sex nivåer av kapsling - detta motiverar direkt till samtal från hjärta till hjärta med författarna.

Försöker fixa koden

Låt oss gå tillbaka till den ursprungliga metoden och försöka fixa det. Jag måste säga direkt att vi inte kommer att fixa objektlåset ännu, bara för att inte komplicera exempelkoden.

Den första metoden för ett typiskt 1C smeknamn

Vanligtvis vet 1C-programmerare att ett undantag kan göras när de skriver. De är också rädda för undantag, så de försöker fånga dem alla. Till exempel, så här:


Procedur Mycket användbar&ImportantCode(List of ReferenceReferences) StartTransaction(); För varje länk från listan över länkar i referensslingan Referensobjekt = Reference.GetObject(); ReferenceObject.WhichField = "Jag har ändrat från kod"; Försök attReferenceObject.Write(); Exception Log.Error("Det gick inte att skriva element %1", referens); Fortsätt; Slut på försök; EndCycle; CommitTransaction(); Slutprocedur

Nåväl, det blev bättre, eller hur? Tills vidare, möjliga misstag poster bearbetas och till och med loggas. Undantag kommer inte längre att göras när du skriver ett objekt. Och i loggen kan du till och med se - på vilket objekt, han var inte för lat, han visade en länk i meddelandet istället för det lakoniska "Kataloginmatningsfelet", som utvecklare som alltid har bråttom ofta gillar att skriva. Det finns med andra ord en oro för användaren och tillväxten av kompetens.


Men ett erfaret 1C smeknamn här kommer att säga att nej, det blev inte bättre. Faktum är att ingenting har förändrats, och kanske till och med värre. I metoden "Write()" kommer själva 1C-plattformen att starta en skrivtransaktion, och denna transaktion kommer redan att vara kapslad i förhållande till vår. Och om, vid tidpunkten för arbetet med databasen, 1C rullar tillbaka sin transaktion (till exempel ett affärslogikundantag kastas), kommer vår toppnivåtransaktion fortfarande att markeras som "bortskämd" och det kommer inte att vara möjligt att fixa det. Som ett resultat kommer den här koden att förbli problematisk, och när du försöker begå kommer den att visa "fel har redan inträffat".


Föreställ dig nu att vi inte pratar om en liten metod, utan om en djup samtalsstack, där någon längst ner tog och "släppte" den påbörjade transaktionen från sin metod. Procedurer på toppnivå kanske inte har någon aning om att någon där nere startade transaktioner. Som ett resultat kraschar hela koden med ett otydligt fel, vilket är omöjligt att undersöka i princip.


Koden som startar en transaktion krävs för att slutföra eller återställa den. Oavsett eventuella undantag. Varje kodsökväg bör undersökas för att se om metoden avslutas utan att begå eller avbryta transaktionen.

Metoder för att arbeta med transaktioner i 1C

Det skulle inte vara överflödigt att komma ihåg att 1C i allmänhet ger oss transaktioner att arbeta med. Dessa är välkända metoder:

  • StartTransaction()
  • CommitTransaction()
  • CancelTransaction()
  • TransactionActive()

De första 3 metoderna är uppenbara och gör vad de säger i deras namn. Den sista metoden returnerar True om transaktionsräknaren är större än noll.


Och det finns en intressant funktion. Transaktionsavslutsmetoderna (Bekräfta och Avbryt) ger undantag om transaktionsantalet är noll. Det vill säga om du ringer någon av dem utanför en transaktion kommer ett fel att uppstå.


Hur använder man dessa metoder korrekt? Mycket enkelt: du måste läsa regeln formulerad ovan:


Hur kan du följa denna regel? Låt oss försöka:


Ovan har vi redan förstått att DoSomething-metoden är potentiellt farlig. Det kan skapa något slags undantag, och transaktionen kommer att "krypa" ut ur vår metod. Okej, låt oss lägga till en möjlig undantagshanterare:


StartTransaction(); Försök att göra något(); Undantag // vad ska man skriva här? Slut på försök; CommitTransaction();

Bra, vi har fångat felet som uppstår, men vad ska man göra med det? Skriv ett meddelande till loggen? Tja, kanske om felloggningskoden skulle vara exakt på denna nivå och vi väntar på felet här. Varom icke? Om vi ​​inte förväntade oss några fel här? Då borde vi bara passera detta undantag ovan, låt ett annat lager av arkitekturen ta itu med dem. Detta görs med operatorn "CauseException" utan argument. I dessa java-sippluses görs detta på exakt samma sätt med throw-satsen.


StartTransaction(); Försök att göra något(); Undantag ThrowException; Slut på försök; CommitTransaction();

Så, vänta... Om vi ​​bara kastar undantaget längre, varför finns det då ett försök överhuvudtaget? Och här är anledningen: regeln tvingar oss att säkerställa slutförandet av transaktionen vi påbörjade.


StartTransaction(); Försök att göra något(); Undantag CancelTransaction(); ThrowException; Slut på försök; CommitTransaction();

Nu verkar det vara vackert. Vi kommer dock ihåg att vi inte litar på DoSomething()-koden. Plötsligt läste författaren inte inuti den här artikeln och vet inte hur man arbetar med transaktioner? Plötsligt tog han det dit och kallade CancelTransaction-metoden, eller vice versa, fixade det? Det är väldigt viktigt för oss undantagshanteraren kastade inte ett nytt undantag, annars kommer det ursprungliga felet att gå förlorat och utredning av problem blir omöjlig. Och vi kommer ihåg att metoderna Commit och Cancel kan skapa ett undantag om transaktionen inte existerar. Det är här som TransactionActive-metoden kommer väl till pass.

slutversion

Slutligen kan vi skriva korrekt, "transaktionssäker" kod. Här är han:


**UPD: Kommentarerna föreslog ett säkrare alternativ när CommitTransaction finns i försöksblocket. Det här alternativet visas här, tidigare var Commit placerat efter försök-undantag-blocket.


StartTransaction(); Försök att göra något(); CommitTransaction(); Undantag If TransactionActive() Then CancelTransaction(); EndIf; ThrowException; Slut på försök;

Vänta, det är inte bara "CancelTransaction" som kan skapa fel. Varför är då "CommitTransaction" inte insvept i samma skick som "TransactionActive"? Återigen, efter samma regel: koden som startade transaktionen bör ansvara för att den slutförs. Vår transaktion är inte nödvändigtvis den allra första, den kan kapslas. På vår abstraktionsnivå behöver vi bara bry oss om vår transaktion. Alla andra borde inte vara av intresse för oss. De är främlingar, vi ska inte ta ansvar för dem. Exakt BÖR INTE. Inget försök bör göras för att ta reda på den verkliga nivån på transaktionsräknaren. Detta kommer återigen att bryta inkapslingen och leda till att transaktionshanteringslogiken "smetas ut". Vi kontrollerade bara för aktivitet i undantagshanteraren och bara för att säkerställa vår hanterare kommer inte att generera ett nytt undantag som "döljer" det gamla.

Refaktoreringschecklista

Låt oss titta på några av de vanligaste situationerna som kräver ingrepp i koden.


Mönster:


StartTransaction(); Göra någonting(); CommitTransaction();

Slå in den i en "säker" konstruktion med Retry, Keepalive och Throw the undantag.


Mönster:


Om inte TransactionActive() Då StartTransaction() EndIf

Analys och refaktorering. Författaren visste inte vad han gjorde. Det är säkert att starta kapslade transaktioner. Du behöver inte kontrollera villkoret, du behöver bara starta en kapslad transaktion. Nedan i modulen perverterar han nog fortfarande där med deras fixering. Detta är garanterat hemorrojder.


Ungefär liknande alternativ:


If TransactionActive() Then CommitTransaction() EndIf

likaså: att begå en transaktion efter villkor är konstigt. Varför finns det ett villkor? Vadå, någon annan kunde redan ha begått den här transaktionen? Anledning till rättstvister.


Mönster:


StartTransaction() While Selection.Next() Loop // läsobjekt genom referens // skrivobjekt EndCycle; CommitTransaction();
  1. införa hanterad låsning för att undvika dödläge
  2. ange metodanrop Blockera
  3. svepa in i "prova" som visas ovan

Mönster:


StartTransaction() While Selection.Next() Loop Attempt Object.Write(); Undantagsrapport("Det gick inte att skriva"); Slut på försök; EndCycle; CommitTransaction();

Denna transaktion kommer inte längre att slutföras i händelse av ett undantag. Det är ingen idé att fortsätta cykeln. Koden måste skrivas om, med hänvisning till den ursprungliga uppgiften. Ge eventuellt ett mer informativt felmeddelande.

Till sist

Jag, som du säkert redan gissat, tillhör människor som älskar 1C-plattformen och utvecklingen på den. Naturligtvis finns det klagomål på plattformen, särskilt i Highload-miljön, men i allmänhet låter den dig snabbt och billigt utveckla företagsapplikationer av mycket hög kvalitet. Att ge out of the box och ORM, och GUI, och webbgränssnitt, och rapportering och mycket mer. I kommentarerna på Habré brukar de skriva vad som helst arrogant, och så, killar, huvudproblemet med 1C, som ekosystem, är inte en plattform eller en leverantör. Detta är en för låg ingångströskel, vilket gör att människor som inte förstår vad en dator, databas, klient-server, nätverk och allt det är för att komma in i branschen. 1C har gjort företagsapplikationsutveckling för enkel. På 20 minuter kan jag skriva ett redovisningssystem för köp/försäljning med flexibla rapporter och webbklient på. Efter det är det lätt för mig att tänka på mig själv att man i stor skala kan skriva på ungefär samma sätt. På något sätt kommer 1C att göra allt inom sig själv, jag vet inte hur, men det kommer förmodligen att göra det. Jag skriver "Starta transaktion ()" ....

Lägg till taggar