मानक कैची संग्रहीत कक्षाओं में, जब एक रिकॉर्ड को संशोधित किया जाता है, तो पिछले संपत्ति मूल्य हमेशा के लिए गायब हो जाते हैं। लेकिन ऐसे समय होते हैं जब यह अवांछनीय होता है, जब "सभी चालें दर्ज की जानी चाहिए।" सबसे पहले, निश्चित रूप से, वित्तीय रूप से जिम्मेदार व्यक्तियों के लिए आवेदन विकसित करते समय ऐसी आवश्यकता उत्पन्न होती है, जिनके लिए अवसर महत्वपूर्ण है, उदाहरण के लिए, गलत कार्रवाई को रद्द करने और किसी दिए गए समय के लिए दस्तावेज़ की स्थिति को पुनर्स्थापित करने के लिए, या अधिक महत्वपूर्ण बात, एक हमलावर द्वारा "कवर अप" के साथ घटना की जांच करने के लिए। डेटाबेस में »।
यह आलेख प्रदर्शित करता है कि Caché ऑब्जेक्ट्स के लिए संस्करण संग्रहण और पुनर्प्राप्ति को कैसे लागू किया जाए।
यह कार्यक्षमता% OnBeforeSave () पद्धति और अद्यतन घटना के लिए SQL ट्रिगर का उपयोग करके लगातार कक्षाओं में जोड़ा जा सकता है। चूंकि बहुसंख्यक मामलों में जहां परसेंट क्लासेस का उपयोग किया जाता है, सम्पूर्ण रिकॉर्ड, जिसमें गुण - सरणियाँ और सूचियाँ शामिल हैं - को स्टोरेज ग्लोबल नोड ऑफ़ फॉर्म ^ क्लासडाटा (आईडी) में संग्रहीत किया जाता है, किसी ऑब्जेक्ट को ओवरराइट करने से पहले, कुछ एकांत जगह में रिकॉर्ड के पिछले संस्करण को सहेजना संभव है। विंडोज में रीसायकल बिन की तरह।
यह लेख उपरोक्त सुविधा का उपयोग करने के एक उदाहरण के साथ है, जिसमें Pers Persist वर्ग -% Persistent inheritor शामिल है, जिसमें आवश्यक तरीके जोड़े जाते हैं, और TestPersHist डेमो क्लास, जिसमें बिल्कुल कुछ भी असामान्य नहीं है, सिवाय इसके कि यह PersHist का उत्तराधिकारी है, और सीधे% नहीं लगातार। ठीक उसी तरह से आप इसमें रिकॉर्ड बना सकते हैं और संशोधित कर सकते हैं, ऑब्जेक्ट और एसक्यूएल एक्सेस दोनों, लेकिन वहां ओवरराइट किया गया डेटा बिना ट्रेस के गायब नहीं होता है और इसे पुनर्स्थापित किया जा सकता है।
हठवादी वर्ग स्रोत///% पर्सेंटेंट क्लास का यह पूर्वज
/// में अपडेट्स रिकॉर्ड करने में सक्षम है इतिहास रिकॉर्डिंग।
क्लास पर्सिस्टिस्ट का% पर्सेंट पर्सेंटेज [ अमूर्त , क्लास टाइप = "", प्रोसीजरब्लॉक ]
{
/// यह कॉलबैक विधि <METHOD>% सहेजें </ METHOD> विधि द्वारा मंगाई गई है
/// सूचना प्रदान करें कि ऑब्जेक्ट सहेजा जा रहा है। इसे पहले कहा जाता है
/// किसी भी डेटा को डिस्क पर लिखा जाता है।
///
/// <P> <VAR> डालने </ VAR> को 1 पर सेट किया जाएगा यदि यह ऑब्जेक्ट पहली बार सहेजा जा रहा है।
///
/// <P> यदि यह विधि कोई त्रुटि देता है तो <METHOD>% सहेजें </ METHOD> पर कॉल विफल हो जाएगी।
विधि % OnBeforeSave ( % बूलियन के रूप में डालें ) % स्थिति के रूप में [ अंतिम , निजी ]
{
q : $ $ $ डालकर ठीक करें
q : '.. % ObjectModified () $$$ ठीक है
q .. SaveLastRevision (.. % Id ())
}
/// यह कॉलबैक विधि <METHOD>% हटाएं </ METHOD> विधि द्वारा लागू की जाती है
/// अधिसूचना प्रदान करें कि <VAR> oid </ VAR> द्वारा निर्दिष्ट वस्तु को हटाया जा रहा है।
///
/// <P> यदि यह विधि कोई त्रुटि देता है, तो ऑब्जेक्ट हटाया नहीं जाएगा।
ClassMethod % OnDelete (% ऑब्जेक्ट ऑब्जेक्ट के रूप में oid ) % स्थिति [ अंतिम , निजी ]
{
s id = $ lg ( oid ) q : ' आईडी 0
घ .. SaveLastRevision ( आईडी )
q $ $ $ ओके
}
ClassMethod GetDataLocation () % स्ट्रिंग के रूप में
{
s ix = ## वर्ग ( % ClassDefinition )। % OpenId (.. % ClassName ) ()
q : ix । भंडार । गिनती () '= 1 ""; परिष्कृत स्टोरेज समर्थित नहीं हैं
q ix । भंडार । GetAt (1)। डेटा स्थान
}
/// सहेजे गए संशोधन की अगली या प्रीविओस ($ o () के समान) रिटर्न
Method OrderIdSave ( आईडी % स्ट्रिंग के रूप में = "" , दिशा % स्ट्रिंग के रूप में = 1 ) % स्ट्रिंग के रूप में
{
s dl = .. GetDataLocation () q : dl = "" ""
s id = .. % Id () q : ' id ""
q $ o (@ dl @ ( "इतिहास" , आईडी , आईडी ), दिशा )
}
/// सहेजे गए संशोधन का टाइमस्टैम्प लौटाता है
विधि GetTimeStampByIdSave ( आईडी % के रूप में स्ट्रिंग ) % स्ट्रिंग के रूप में
{
s dl = .. GetDataLocation () q : dl = "" ""
s id = .. % Id () q : ' id ""
q : $ d (@ dl @ ( "इतिहास" , आईडी , आईडी )) <10 ""
q $ o (@ dl @ ( "इतिहास" , आईडी , आईडी , "" ))
}
/// बनाता संशोधन फिर से निर्दिष्ट वास्तविक समय पर या बाद में बचाया
/// और इसे लोड करें। सभी सहेजे न गए परिवर्तन खो जाएंगे।
विधि MakeActual ( % StringTimeStamp के रूप में टाइमस्टैम्प ) % की स्थिति
{
s dl = .. GetDataLocation () q : dl = "" 0 ; असमर्थित भंडारण
s id = .. % Id () q : ' id 0 ; कभी नहीं बचाया
s zts = टाइमस्टैम्प
; zts> = टाइमस्टैम्प के साथ सहेजे गए संशोधन को खोजना
i $ d (@ dl @ ( "इतिहास" , "ZI" , id , zts )) का आईडी = $ o (@ dl @ ( "इतिहास" , "ZI" , id , zts , "" ), - 1)
e s zts = $ o (@ dl @ ( "History" , "ZI" , id , "" )) q : zts = "" 0 s ids = $ o (@ dl @ ( "इतिहास" , "ZI" , id) , zts , "" ))
q $ s ( ids : .. MakeActualByIdSave ( ids ), 1: 0)
}
/// बनाता है फिर से संशोधन वास्तविक बचाया और इसे idsave द्वारा लोड।
/// सभी सहेजे न गए परिवर्तन खो जाएँगे।
विधि MakeActualByIdSave ( आईडी % के रूप में स्ट्रिंग ) % स्थिति के रूप में
{
s dl = .. GetDataLocation () q : dl = "" 0 ; असमर्थित भंडारण
s id = .. % Id () q : ' id 0 ; कभी नहीं बचाया
q : $ d (@ dl @ ( "इतिहास" , आईडी , आईडी )) <१० ०
s zts = $ o (@ dl @ ( "History" , id , ids , "" )) q : zts "" 0
घ .. SaveLastRevision ( आईडी )
k @ dl @ ( id ) m @ dl @ ( id ) = @ dl @ ( "इतिहास" , id , ids , zts )
डी .. % रीलोड () डी .. % सेटमोडिफाइड (1)
क्यू .. % बचाओ () ; सूचकांकों को सही बनाने के लिए फिर से सहेजें
}
/// हटाए गए रिकॉर्ड के अगले या पिछले ($ o () के समान) रिटर्न देता है
ClassMethod OrderDeletedId ( आईडी % स्ट्रिंग = "" , दिशा %% स्ट्रिंग के रूप में = 1 ) % स्ट्रिंग [ अंतिम ] के रूप में
{
s dl = .. GetDataLocation () q : dl = "" 0
f s id = $ o (@ dl @ ( "History" , id ), दिशा ) q : id = "" q : ' $ d (@ dl @ ( id ))
क्यू आईडी
}
ClassMethod SaveLastRevision ( आईडी ) % की स्थिति [ अंतिम ]
{
s id = $ g ( id ) q : ' आईडी $ $$ OK ; बचाने के लिए कोई पिछला संशोधन नहीं
s dl = .. GetDataLocation () q : dl = "" $$$ ठीक है
s ids = $ i (@ dl @ ( "History" , id )), zts = $ zu (188) ; स्थानीय समय क्षेत्र में $ ZTIMESTAMP
m @ dl @ ( "इतिहास" , id , ids , zts ) = @ dl @ ( आईडी )
s @ dl @ ( "इतिहास" , "ZI" , id , zts , ids ) = ""
q $ $ $ ओके
}
ClassMethod UnDeleteId ( आईडी % के रूप में आईडी ) % की स्थिति [ अंतिम ]
{
s dl = .. GetDataLocation () q : dl = "" 0
q : ' $ g ( आईडी ) 0
q : $ d (@ dl @ ( id )) 0 ; हटाया नहीं गया
q : $ d (@ dl @ ( "इतिहास" , आईडी )) '> १ ० ; कोई बचाया संशोधन नहीं
; नवीनतम संशोधन खोज रहा है
s आईडी = $ o (@ dl @ ( "इतिहास" , आईडी , "" ), - १) q : ' आईडी ०
s zts = $ o (@ dl @ ( "History" , id , ids , "" ), - 1) q : zts = "" 0
m @ dl @ ( id ) = @ dl @ ( "इतिहास" , id , ids , zts )
s ix = .. % OpenId ( id ) d ix । % सेटमोडिफ़ाइड (1) का Res = ix । % सेव () k ix
क्यू रेस
}
ट्रिगर OnBeforeDeleteForSQL [ घटना = DELETE]
{
n आईडी , ix s id = {आईडी} q : ' आईडी
घ .. SaveLastRevision ( आईडी )
क्ष
}
ट्रिगर OnBeforeSaveForSQL [ घटना = अद्यतन]
{
n आईडी , ix s id = {आईडी} q : ' आईडी
घ .. SaveLastRevision ( आईडी )
क्ष
}
}
कक्षा स्रोत TestPersHist/// टेस्ट कैसे करें
///
/// // एक उदाहरण बनाएँ और आरंभ करें:
///
/// s ix = ## वर्ग (TestPersHist)।% नया ()
/// s ix.TestVal = $ H
/// w ix.TestArray.SetAt ("Abra", 1)
/// w ix.TestArray.SetAt ("शुभ्रा", 2)
/// w ix.TestList.InsertAt ("Cadabra", 1)
/// s id = ix।% Id ()
/// w ix।% सहेजें () k ix
///
/// // अपडेट करने का प्रयास करें
///
/// s ix = ## वर्ग (TestPersHist)।% OpenId (आईडी)
/// s ix.TestVal = $ J
/// w ix.TestArray.SetAt ("स्विम", 3)
///
/// w ix।% सहेजें () k ix
///
/// // वैश्विक ^ User.TestPersHistD को देखें। सभी परिवर्तन दर्ज!
क्लास टेस्टपर्सिस्ट पर्सिस्टिस्ट का विस्तार करता है [ क्लास टाइप = लगातार, प्रक्रियात्मक ]
{
% स्ट्रिंग ( MAXLEN = 255 ) की सरणी के रूप में संपत्ति परीक्षण ; // [संग्रह = सरणी];
गुण स्ट्रिंग% स्ट्रिंग की सूची के रूप में ; // [संग्रह = सूची];
प्रॉपर्टी टेस्टवैल % स्ट्रिंग के रूप में ( MAXLEN = 255 ) [ आवश्यक ];
}
मैं पर्सिस्ट क्लास में जोड़े गए तरीकों पर संक्षेप में टिप्पणी करूंगा।
वर्ग विधि GetDataLocation () - वंशज वर्ग का विवरण पढ़ता है और भंडारण वैश्विक का नाम देता है।
कक्षा विधि SaveLastRevision (आईडी) हमारी कक्षा का केंद्रीय आंकड़ा है। एक अच्छे तरीके से, इसे निजी बनाया जाना चाहिए, लेकिन इस मामले में इसे एसक्यूएल ट्रिगर से नहीं बुलाया जा सकता है। यह विधि वैश्विक नोड ^ ClassData (आईडी) से ^ ClassData ("इतिहास", आईडी, आईडी की बचत_वर्ष, $ ZTIMESTAMP) के रिकॉर्ड की प्रतिलिपि बनाती है। स्वाभाविक रूप से, ^ ClassData के बजाय, GetDataLocation () द्वारा लौटाए गए संग्रहण वैश्विक का वास्तविक नाम उपयोग किया जाता है।
निजी विधि% OnBeforeSave () - SaveLastRevision () को केवल तभी सहेजता है जब% Save () विधि डेटाबेस में पहले से संग्रहीत रिकॉर्ड का वास्तविक संशोधन करती है।
OnBeforeSaveForSQL () SQL ट्रिगर ही करता है।
और, अंत में, MakeActual (टाइमस्टैम्प) विधि का उपयोग उस डेटा को राज्य में पुनर्स्थापित करने के लिए किया जाता है जो $ ZTIMESTAMP प्रारूप (अधिक सटीक, $ ZU (188) - स्थानीयकृत $ ZIMIMESTAMP) में समय पर दिए गए बिंदु पर था। यह विधि यह देखने के लिए है कि क्या रिकॉर्ड निर्दिष्ट समय पर या उसके बाद अपडेट किया गया था, और यदि ओवरराइट किया गया है, तो यह रिकॉर्ड के वर्तमान संस्करण को बचाता है और पहले से सहेजे गए को पुनर्स्थापित करता है। उदाहरण के लिए
ओरफ करें । MakeActual (( $ h -7) _ "," _ (12.5 * 3600))यह उस राज्य में रिकॉर्ड लौटा देगा जिसमें यह एक सप्ताह पहले 12:30 बजे था, और निश्चित रूप से, यह अपील के समय इस रिकॉर्ड की स्थिति की एक प्रति बचाएगा।
यदि इस ऑब्जेक्ट की बचत की पूरी सूची की आवश्यकता होती है, तो कॉल का निम्नलिखित क्रम प्रस्तावित है।
s ids = "" f s id = oref । OrderIdSave ( ids ) q : ids = "" w ids,!सहेजे गए संस्करणों के सभी पहचानकर्ताओं को बाहर कर देगा। एक विधि
लिखो । GetTimeStampByIdSave ( आईडी )इच्छुक संस्करण को सहेजने का सही समय लौटाएगा। हालाँकि, आप इसे सीधे पहचानकर्ता द्वारा पुनर्स्थापित कर सकते हैं:
ओरफ करें । MakeActualByIdSave ( आईडी )लेकिन रिकॉर्ड को संशोधित करने पर सभी परिवर्तनों को सहेजना अतार्किक होगा, और साथ ही इसे हटाए जाने पर इस रिकॉर्ड को स्थायी रूप से खोने दें। निजी% OnDelete () विधि और OnBeforeDeleteForSQL एसक्यूएल ट्रिगर, जो आपको हटाए गए रिकॉर्ड को पुनर्स्थापित करने की अनुमति देगा अंडरटेइल्ड (आईडी) वर्ग विधि का उपयोग करके, इस उपद्रव के खिलाफ बीमा किया जाता है।
बहाल किए जाने वाले रिकॉर्ड की पहचानकर्ता की गणना कैसे करें यह एक गैर-तुच्छ प्रश्न है, और यह तय किया जाना चाहिए, जाहिर है, पहले से ही अंतिम आवेदन के डेवलपर के विवेक पर। लेकिन किसी भी मामले में, आपको हटाए गए रिकॉर्ड के माध्यम से नेविगेट करने में सक्षम होने की आवश्यकता है। और यह अवसर OrderDletletId (id, dir) विधि द्वारा प्रदान किया जाता है, जो समान रूप से प्रसिद्ध $ ORDER () फ़ंक्शन के समान है, निकटतम रिकॉर्ड या पिछले (dir = -1 के साथ) एक रिकॉर्ड के पहचानकर्ता को हटा दिया गया था जिसे हटा दिया गया था लेकिन इतिहास में सहेजा गया था।
Pershist से विरासत में लिया गया डेमो क्लास TestPersHist में एक नियमित क्षेत्र, सूची और सरणी के प्रारूप में कुछ भी नहीं है। यह इस वर्ग के रिकॉर्ड्स को मनमाने ढंग से बनाने और संशोधित करने का प्रस्ताव है (उदाहरण के लिए, वर्ग टिप्पणियों को देखें), जिसके बाद सीधे भंडारण वैश्विक को देखकर ^ User.TestPersHistD, Caché के मानक टूल का उपयोग करके वैश्विक भंडारण की सामग्री में परिवर्तन को ट्रैक कर सकता है और अपने आप को देख सकता है कि सभी गुणों के सभी संशोधन ^ उपयोगकर्ता शाखा में सहेजे गए हैं। .TestPersHistD ("इतिहास")।
Caché में आयात के लिए तैयार फॉर्म में PersHist और TestPersHist वर्गों का कोड
इस लिंक पर डाउनलोड के लिए उपलब्ध है: Caché संस्करण 5.0 के लिए CDL प्रारूप में और इससे पहले, आधुनिक लोगों के लिए XML प्रारूप में।