1001 Things You Wanted To Know About Visual FoxPro phần 6 pptx

50 416 1
1001 Things You Wanted To Know About Visual FoxPro phần 6 pptx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Chapter 7: Working with Data 223 CLOSE ALL RETURN PROCEDURE CheckStProc DO dummy DO OnlyaCH07 DO OnlybCH07 If you run this program from the command line you will see that with multiple DBCs open, calling a stored procedure which exists only in one DBC is fine.It does not matter which DBC is current, the correct procedure is located. However, if both DBCs contain a stored procedure that is named the same, then the setting of the DBC is vitally important since Visual FoxPro will always search the current database first and only then will it search any other open databases. Finally if NO DBC is defined as current, then Visual FoxPro executes the first procedure it finds – in this example it is always the 'dummy' procedure in the aCH07 database container. Reversing the order in which the two DBCs are opened in the ShoStPro program changes the result for the last test. This suggests that Visual FoxPro is maintaining an internal collection of open databases, in which the last database to be opened is at the head of the list, and is searched first, when no database is set as current. How to validate a database container Visual FoxPro provides a VALIDATE DATABASE command that will run an internal consistency check on the currently open database. Currently (Version 6.0a) this command can ONLY be issued from the command window and by default its results are output to the main FoxPro screen. Attempting to use it within an application causes an error. You can validate a database without first gaining exclusive use, but the DBC index will not be re-built, nor will you be able to fix any errors that the process may find. With exclusive use you may choose either to rebuild the index (a plain VALIDATE DATABASE command will do just that) or to invoke the repair mechanism by adding the ‘ RECOVER ’ clause to the command. While not very sophisticated, the recovery option at least highlights anything that VFP feels is wrong with your DBC and offers you options to either locate or delete a missing item and to delete or re-build missing indexes (providing that the necessary information is available in the DBC itself). The best way to avoid problems in the DBC is to ensure you always make changes to its tables (or views) through the DBC’s own mechanisms. Avoid actions like building temporary indexes outside the DBC or programmatically changing view or table definitions without first getting exclusive use of the DBC. In short, while not exactly fragile, the DBC relies heavily on its own internal consistency and errors (real or imagined) will inevitably cause you problems sooner or later. How to pack a database container The Visual FoxPro database container is, itself, a normal Visual FoxPro table in which each row contains the stored information for one object in the database. Like all Visual FoxPro tables, deleting a record only marks that record for deletion and the physical record is not 224 1001 Things You Always Wanted to Know About Visual FoxPro removed from the DBC. This means, over time, that a database can get large, even though it actually contains very few current items. The PACK DATABASE command is the only method that you should use to clean up a DBC. Simply opening the DBC as a table and issuing a standard PACK command is not sufficient because the DBC maintains an internal numbering system for its objects that will not be updated unless the PACK DATABASE command is used. Using this command requires that you gain exclusive use to the database. Moving a database container We mentioned earlier in this section that the only price for gaining all the functionality that a database container provides is a minor modification to the header of the table to include a backlink to the DBC. This is, indeed, a trivial thing UNTIL you try to move a database container. Then its significance can assume monstrous proportions. The reason is that Visual FoxPro stores the relative path from the table back to the owning DBC directly in the table header. Providing that you always keep the DBC and all of its data tables in the same directory, all will be well because all that gets stored is the name of the database container. However, when you have a database container that is NOT in the same directory as its tables you are laying yourself open to potential problems. We created a table in our working directory (C:\VFP60\CH07) and attached it to a database that resided in the C:\TEMP directory. The backlink added to the table was: \ \TEMP\TESTDBC.DBC After moving this database container to the C:\WINDOWS\TEMP directory, any attempt to open the table resulted in a ‘cannot resolve backlink’ error and the option to either locate the missing DBC, delete the link and free the table (with all the dire consequences for long field names that this entails) or to cancel. Being optimists we chose to locate the database container and were given a dialog to find it. Unfortunately having found our DBC and selected it, we were immediately confronted with Error 110 informing us that the "File must be opened exclusively" Not very helpful! Fixing the backlink for a table So what can be done? Fortunately the structure of the DBF Header is listed in the Help file (see the "Table File Structure" topic for more details) and Visual FoxPro provides us with some neat low level file handling functions which allow us to open a file and read and write to it at the byte level. So we can just write in the new location for the DBC and all will be well. The only question is where to write it? You will see from the Help file that the size of the table header is actually determined by the formula: 32 + ( nFields * 32) +264 bytes Where nFields is the number of fields in the table, which we could get using FCOUNT() – if we could only open the table! (There is also a HEADER() – a useful little function that actually Chapter 7: Working with Data 225 tells us how big the table header is. Unfortunately it also requires that we be able to open the table.) But if we could open the table, we wouldn’t need to fix the backlink either. The backlink itself is held as the last 263 bytes of the table header. However, the only certain way of getting those vital 263 bytes is to try and read the maximum possible number of bytes that could ever be in a table header. (Trying to read beyond the end of file does not generate an error in a low level read, it just stops at the end of file marker.) Visual FoxPro is limited to 255 fields per record so we need, using the formula above, to read in 8,456 bytes. This is well within the new upper limit of 16,777,184 characters per character string or memory variable so all is well. Fortunately the field records section of the header always ends with a string of 13 " NULL" characters (ASCII Character 0) followed by a " Carriage Return " (ASCII Character 13). So if we locate this string within the block we have read from the table, we will have the start of the backlink. The following function uses this technique to read the backlink information from a table (when only the table file name is passed) or to write a new backlink string (pass both the file name and the new backlink): ********************************************************************** * Program : BackLink.prg * Compiler : Visual FoxPro 06.00.8492.00 for Windows * Abstract : Sets/Returns Backlink Information from a table * : Pass both DBF File name (including extension) only to * : to return backlink, also pass new backlink string to * : write a new backlink ********************************************************************** LPARAMETERS tcTable, tcDBCPath LOCAL lnParms, lnHnd, lnHdrStart, lnHdrSize, lcBackLink, lcNewLink lnParms = PCOUNT() *** Check that the file exists IF ! FILE( tcTable ) ERROR "9000: Cannot locate file " + tcTable RETURN .F. ENDIF *** Open the file at low level - Read Only if just reading info lnHnd = FOPEN( tcTable, IIF( lnParms > 1, 2, 0) ) *** Check file is open IF lnHnd > 0 *** Backlink is last 263 bytes of the header so calculate position *** Max header size is (32 + ( 255 * 32 ) + 264) = 8456 Bytes lcStr = FREAD( lnHnd, 8456 ) *** Field records end with 13 NULLS + "CR" lcFieldEnd = REPLICATE( CHR(0), 13 ) + CHR(13) lnHeaderStart = AT( lcFieldEnd, lcStr ) + 13 *** Move file pointer to header start position FSEEK( lnHnd, lnHeaderStart ) *** Read backlink lcBackLink = UPPER( ALLTRIM( STRTRAN( FGETS( lnHnd, 263 ), CHR(0) ) ) ) *** If we are writing a new backlink IF lnParms > 1 *** Get the path (max 263 characters!) tcDBCPath = LEFT(tcDBCPath,263) *** Pad it out to the full length with NULLS lcNewLink = PADR( ALLTRIM( LOWER( tcDBCPath ) ), 263, CHR(0) ) *** Go to start of Backlink FSEEK( lnHnd, lnHeaderStart ) 226 1001 Things You Always Wanted to Know About Visual FoxPro *** Write the new backlink information FWRITE( lnHnd, lcNewLink ) *** Set the new backlink as the return value lcBackLink = tcDbcPath ENDIF *** Close the file FCLOSE(lnHnd) ELSE ERROR "9000: Unable to open table file" lcBackLink = "" ENDIF *** Return the backlink RETURN lcBackLink What happens to views when I move the database container? The good news is that moving a database container has no effect on views. Views are stored as SQL statements inside the database container and, although they reference the DBC by name, they do not hold any path information. So there is no need to worry about them if you move a DBC from one location to another (phew!). Renaming a database container Renaming a database container presents a different set of problems. This time, both tables and views are affected. Tables will be affected because of the backlink they hold - which will end up pointing to something that no longer exists. However, this is relatively easy to fix, as we have already seen, and can easily be automated. In this case, though, Views will be affected because Visual FoxPro very helpfully includes the name of the DBC as part of the query that is stored. Here is part of the output for a query (generated by the GENDBC.PRG utility that ships with Visual FoxPro, and which can be found in the VFP\Tools sub-directory): FUNCTION MakeView_TESTVIEW ***************** View setup for TESTVIEW *************** CREATE SQL VIEW "TESTVIEW" ; AS SELECT Optutil.config, Optutil.type, Optutil.classname FROM testdbc!optutil DBSetProp('TESTVIEW', 'View', 'Tables', 'testdbc!optutil') * Props for the TESTVIEW.config field. DBSetProp('TESTVIEW.config', 'Field', 'KeyField', .T.) DBSetProp('TESTVIEW.config', 'Field', 'Updatable', .F.) DBSetProp('TESTVIEW.config', 'Field', 'UpdateName', 'testdbc!optutil.config') DBSetProp('TESTVIEW.config', 'Field', 'DataType', "C(20)") * Props for the TESTVIEW.type field. DBSetProp('TESTVIEW.type', 'Field', 'UpdateName', 'testdbc!optutil.type') * Props for the TESTVIEW.classname field. DBSetProp('TESTVIEW.classname', 'Field', 'UpdateName', 'testdbc!optutil.classname') ENDFUNC Notice that the database container is prepended to the table name on every occasion - not just in the actual SELECT line but also as part of each field’s "updatename" property. This is, no Chapter 7: Working with Data 227 doubt, very helpful when working with multiple database containers but is a royal pain when you need to rename your one and only DBC. Unfortunately we have not been able to find a good solution to this problem. The only thing we can suggest is that if you use Views, you should avoid renaming your DBC if at all possible. If you absolutely must rename the DBC, then the safest solution is to run GENDBC.PRG before you rename it and extract all of the view definitions into a separate program file that you can then edit to update all occurrences of the old name with the new. Once you have renamed your DBC simply delete all of the views and run your edited program to re-create them in the newly renamed database. Note: The SQL code to generate a view is stored in the "properties" field of each "View" record in the database container. Although it is stored as object code, the names of fields and tables are visible as plain text. We have seen suggestions that involve hacking this properties field directly to replace the DBC name but cannot advocate this practice! In our testing it proved to be a thoroughly unreliable method which more often than not rendered the view both unusable and unable to be edited. Using GENDBC may be less glamorous, but it is a lot safer! Managing referential integrity in Visual FoxPro The term "referential integrity", usually just abbreviated to "RI", means ensuring that the records contained in related tables are consistent. In other words that every child record (at any level) has a corresponding parent, and that any action that changes the key value used to identify a parent is reflected in all of its children. The objective is to ensure that 'orphan' records can never get into, or be left in place in, a table. Visual FoxPro introduced built-in RI rules in Version 3.0. They are implemented by using the persistent relationships between tables defined in the database container and triggers on the tables to ensure that changes to key values in tables are handled according to rules that you define. The standard RI builder allows for three kinds of rule as follows: • Ignore: The default setting for all actions, no RI is enforced and any update to any table is allowed to proceed - exactly as in earlier versions of FoxPro • Cascade: Changes to the key value in the parent table are automatically reflected in the corresponding foreign keys in all of the child tables to maintain the relationships • Restrict: Changes which would result in a violation of RI are prohibited Setting up RI in Visual FoxPro is quite straightforward and the RI builder handles all of the work for you. Figure 7.3, below, shows the set-up in progress for a simple relational structure involving four tables (We have used the tables from the VFP Samples "TestData" database to illustrate this section). So far the following rules have been established: 228 1001 Things You Always Wanted to Know About Visual FoxPro Table 7.4 Typical RI Rule set-up Parent Table Child Table Action Rule Customer Orders Insert Ignore Customer Orders Update Cascade Customer Orders Delete Restrict Orders OrdItems Insert Restrict Orders OrdItems Update Cascade Orders OrdItems Delete Restrict The consequences of these rules are that a user can always add a new customer (Ignore Customer Insert), but cannot delete a customer who has orders on file (Restrict Customer Delete) and any change to a customer’s key will update the corresponding order keys (Cascade Customer Update). For the orders table the rules are that an order item may only be inserted against a valid order (Restrict Order Insert), that no order may be deleted while it has items associated with it (Restrict Order Delete) and that any changes to an order’s key value will be reflected in all of the items to which it refers (Cascade Order Update). Figure 7.3 Using the VFP RI builder Limitations of the generated RI Code Unfortunately the implementation in Visual FoxPro is not very efficient - generating RI rules for a lot of tables results in an enormous amount of classical 'xBase-style' procedural code Chapter 7: Working with Data 229 being added to the Stored Procedures in your database container. The rules defined in the example above resulted in 636 lines of code in 12 separate procedures. Each table has its own named procedure for each trigger generated - thus in the example above procedures are generated named: PROCEDURE __RI_DELETE_customer PROCEDURE __RI_DELETE_orders PROCEDURE __RI_INSERT_orditems procedure __RI_UPDATE_customer procedure __RI_UPDATE_orders procedure __RI_UPDATE_orditems (Note the inconsistency in capitalization of the ‘ PROCEDURE ’ key word!) Adding more tables and more rules increases the amount of code. Moreover this code is not well commented, and in early versions, contained several bugs which could cause it to fail under certain conditions - at least one of which has persisted through into the latest version of Visual FoxPro. The following code is taken directly from the stored procedures generated by VFP V6.0 (Build 8492) for the example shown above: procedure RIDELETE local llRetVal llRetVal=.t. IF (ISRLOCKED() and !deleted()) OR !RLOCK() llRetVal=.F. ELSE IF !deleted() DELETE IF CURSORGETPROP('BUFFERING') > 1 =TABLEUPDATE() ENDIF llRetVal=pnerror=0 ENDIF not already deleted ENDIF UNLOCK RECORD (RECNO()) RETURN llRetVal You will notice that the italicized line of code is incorrectly placed, and should be outside of the IF !DELETED() block. As it stands, the return value from this code may be incorrect (depending on the value of 'pnerror' at the time) if the record being tested is already marked for deletion. Apart from this specific bug, and despite the criticisms leveled at the mechanism for generating the RI code, the code actually works well when used with tables that are structured in the way that was expected. It is certainly easier to use the RI builder than to try and write your own code! There is, however, one additional caution to bear in mind when using the RI builder. Using compound keys in relationships When generating the RI code for the tables in the example, this warning was displayed: 230 1001 Things You Always Wanted to Know About Visual FoxPro Figure 7.4 Warning! What on earth does this mean? Clearly it is a serious warning or Visual FoxPro would not generate it! The answer is that the regular index used as the target for the persistent relationship between the Orders and OrdItems table is actually based on a compound key comprising the foreign key to the Orders Table plus the Item Number. The index expression in question is the one used on the child table orditems, which is actually " order_id+STR(line_no,5,0) ". (This was set up so that when items are displayed they will appear in line number order. Not really an unreasonable thing to do!) However any attempt to insert or change a record in the child table (orditems), will cause a 'Trigger Failed' error. The problem is that any rule set up on this table which needs to refer to the parent table will use, as a key for the SEEK() , the concatenated field values from the child table. This will, obviously, always fail since the key to the parent table ('orders') is merely the first part of the concatenated key in the child. As the warning says, you can (fairly easily) edit the generated code so that the correct key value is used in these situations. The following extract shows the problem: PROCEDURE __RI_UPDATE_orditems *** Other code here *** *** Then we get the old and new values for the child table SELECT (lcChildWkArea) *** Here is where the error arises!!! lcChildID=ORDER_ID+STR(LINE_NO,5,0) lcOldChildID=oldval("ORDER_ID+STR(LINE_NO,5,0)") *** Other code here *** *** If the values have changed, we have a problem!!! IF lcChildID<>lcOldChildID pcParentDBF=dbf(lcParentWkArea) *** And here is where it all goes wrong llRetVal=SEEK(lcChildID,lcParentWkArea) *** And here is where the actual error is generated IF NOT llRetVal DO rierror with -1,"Insert restrict rule violated.","," IF _triggerlevel=1 DO riend WITH llRetVal ENDIF at the end of the highest trigger level ENDIF this value was changed Chapter 7: Working with Data 231 Since the key for the Child ID is hard-coded when the RI code is generated, the actual edit required is very simple - just delete the concatenation. Unfortunately your changes will only hold good as long as you do not re-generate the RI code. The real answer to this problem, however, is very simple. Just don’t do it! As we have already suggested, surrogate keys should always be used to relate tables so that you have an unambiguous method of joining two tables together. In addition to their other virtues, they also ensure that you never need to use compound keys in order to enforce RI. What about other RI options? The native builder handles only three possible alternatives when enforcing RI- Cascade, Restrict and Delete - and makes the assumption that 'orphan' records are always a "bad thing". In practice this is not necessarily the case (providing that you design for that contingency!) and there is at least one case in which an 'Adopt' option would be useful. Consider the situation when a salesman, who is responsible for a group of customers and hence for their orders, leaves the company. Naturally we need to indicate that that salesman’s ID is no longer valid as the salesman responsible for those customers and we need to assign a new person. But what about orders the salesman actually took while working for the company? If we simply delete the salesman, an RI "Restrict" rule would disallow the delete because there are "child" records for that key. What is really needed is a variant on the deletion rule that says: "If the parent record is being deleted, and a valid key is supplied assign any existing child records to the specified key." This is not yet available in Visual FoxPro, but a trigger that enforces such a rule would be easy enough to create, the pseudo code is simply: Check to see if any records exist referencing the key to be deleted If none, allow the delete and exit If a new, valid, key has been specified Change all existing child records to the new key Delete the original parent record Exit Want more details on RI? For more details on the subject of RI in Visual FoxPro and an example of a complete SQL- based alternative to the native RI code, we can do no better than refer you to Chapter 6 of the excellent "Effective Techniques for Application Development with Visual FoxPro 6.0," by Jim Booth and Steve Sawyer (Hentzenwerke Publishing, 1998). Using triggers and rules in Visual FoxPro First we need some definitions. Triggers and rules can only be implemented for tables that are part of a database container to allow the developer to handle issues relating to data integrity. Triggers only fire when data is actually written to the physical table - so they cannot be used for checking values as they are entered into a buffered table. There are actually two sets of rules available - Field Rules and Table rules. Both field level and table level (or more 232 1001 Things You Always Wanted to Know About Visual FoxPro accurately, 'Row Level') rules can reference or modify any single field, or combination of fields in the current row. Rules are fired whenever the object to which they refer loses focus - whether the table is buffered or not - and so can be used for validating data as it is entered. So what’s the practical difference between a ‘trigger’ and a ‘rule’? A trigger, as implemented in Visual FoxPro, is a test that is applied whenever the database detects a change being written to one of its tables. There are, therefore, three types of triggers - one for each of the types of change that can occur (Insert, Update and Delete). The essence of a trigger is that the expression which calls it must return a logical value indicating whether the change should be allowed to proceed or not. This is simplest if the trigger itself always returns a logical value, so if a trigger returns a value of .F. an error is raised (Error #1539 - ‘Trigger Failed’) and the change is not committed to the underlying table. Triggers cannot be used to change values in the record that has caused them to fire, but they can be used to make changes in other tables. A very common use of triggers is, therefore, for the creation of audit logs! (By the way, if you are wondering why this restriction exists, just consider what would happen if you could change the current row in code that was called whenever changes in the current row were detected!). Like triggers, calls to rules must also return a logical value; but unlike triggers, they can make changes to the data in the row to which they refer. Also, since they do fire for buffered tables, rules can be used for performing validation directly in an application’s UI thereby avoiding the necessity of writing code directly in the interface. A rule can also be used to modify fields that are not part of the UI (for example a "last changed" date, or a user id field). In practice the only difference between Field and Table rules is when they are fired. Both triggers and rules apply (like any other stored procedure) whether the table in question is opened in an application or just in a Browse window. The differences between Triggers and Rules can be summarized like this: Table 7.5 Differences between Triggers and Rules Item Fires Capability Field Rule When field loses focus Can reference or modify any field in the record Table Rule When the record pointer moves Can reference or modify any field in the record Trigger When data is saved to disk Cannot modify data in the record that fired the trigger Why, when adding a trigger to a table, does VFP sometimes reject it? Normally this indicates that the data that you have in your table already conflicts with the rule you are trying to apply.(You will sometimes see a similar problem when trying to add a candidate index to a populated table.) When you alter a table to add a trigger or rule, Visual FoxPro applies that test to all existing records - if the data in an existing record fails the rule, then you will get this error. The only solution is to correct the data before re-applying the rule. [...]... source table, instead they go into a 'holding area' (the 'Buffer') until such time as you instruct Visual FoxPro to either save them to permanent storage or discard them Figure 8.1 illustrates the concept This holding area is what you actually 'see' in Visual FoxPro when using a buffered table and is, in reality, an 240 1001 Things You Always Wanted to Know About Visual FoxPro updateable cursor based... about the Refresh() function, and its use, in the section on Views 258 1001 Things You Always Wanted to Know About Visual FoxPro So how do I actually detect conflicts? Before getting into the discussion of how to detect a conflict, let us be clear about what Visual FoxPro' s definition of a conflict is As intimated earlier, Visual FoxPro makes two copies of a record whenever a user accesses data One... the topic) also states that: 2 46 1001 Things You Always Wanted to Know About Visual FoxPro KEYMATCH( ) returns the record pointer to the record on which it was originally positioned before KEYMATCH( ) was issued Hang on right there! Surely 'returns the record pointer' implies that it moves the record pointer - which indeed it does The consequence is that if you are using row buffering and want to check... would be perfectly possible to amend the code to address these issues (perhaps by returning a numeric value indicating different conditions rather than a simple 'yes/no') In our 248 1001 Things You Always Wanted to Know About Visual FoxPro view, however, that might make the function too specific to qualify as a candidate for a generic procedure file which is where we would want to place the function Gotcha!... before being applied If this is a feature of your application it may actually be better to add a separate stored procedure to test whether rules should be applied or not and call it from every trigger or rule Thus: 234 1001 Things You Always Wanted to Know About Visual FoxPro FUNCTION ApplyRules LOCAL llRetVal STORE T TO llRetVal *** Test for presence of the disabling variable IF VARTYPE( glDisableRules... illustrates how triggers can be used to build an audit log The form can be run stand-alone directly from the command line using the DO FORM command The tables have been constructed as follows (the fields prefixed with a '#' are the primary keys): 2 36 1001 Things You Always Wanted to Know About Visual FoxPro Figure 7.5 Audit Logging Tables Triggers on the "Stock" table add data to the audit tables whenever... form? The short answer, as always, is 'it depends' If you use the form's dataenvironment to open tables then you can normally leave the choice of buffering to Visual FoxPro Otherwise you can simply use the CursorSetProp() function in your code to set up each table as required Either way you need to be aware of the consequences so that you can code your update routines appropriately Using BufferModeOverride... required mode in the Options dialog and forget about it All tables will always be opened with that setting and you will always know where you are If you use a Private DataSession then you need to do two things Firstly you must ensure that the environment is set up to support buffering A number of environment settings are scoped to the datasession and you may need to change the default behavior of some, or... synchronized Managing the scope of updates Since Visual FoxPro supports both Row and Table buffering, both of the data transfer functions can operate either on the 'current record only' or on 'all changed records.' The first parameter that is passed to either function determines the scope and, alas, we have another 250 1001 Things You Always Wanted to Know About Visual FoxPro source of possible confusion right... In fact we might have added these buttons directly to the form class itself but have not done so because we can never be sure that we will always want both buttons (Remember - you cannot 2 56 1001 Things You Always Wanted to Know About Visual FoxPro delete an object that is defined by the parent class in either a sub-class or an instance.) This follows from our assertion (see Chapter 3) that if the . are the primary keys): 2 36 1001 Things You Always Wanted to Know About Visual FoxPro Figure 7.5 Audit Logging Tables Triggers on the "Stock" table add data to the audit tables whenever. established: 228 1001 Things You Always Wanted to Know About Visual FoxPro Table 7.4 Typical RI Rule set-up Parent Table Child Table Action Rule Customer Orders Insert Ignore Customer Orders Update. warning was displayed: 230 1001 Things You Always Wanted to Know About Visual FoxPro Figure 7.4 Warning! What on earth does this mean? Clearly it is a serious warning or Visual FoxPro would not generate

Ngày đăng: 05/08/2014, 10:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan