a vfp-sql server application from the begining

Upload: mohsincomputers3076

Post on 09-Oct-2015

43 views

Category:

Documents


1 download

DESCRIPTION

A Book about building Client/Server application with Visual FoxPro and MS SQL Server.

TRANSCRIPT

  • A VFP/SQL Server application from the begining - Part I

    Frederico Tomazetti

    This article is the first of a series in which I will try to show you how to develop a Visual FoxPro system using the SQL Server database. This article series is targeted to VFP developers who use free tables or tables in a VFP DBC and who are trying to learn new development methodologies.

    To this task I will use Erwin 4.0 as a modeling tool, SQL Server 2000 and Visual FoxPro 7.0.

    Here I will make some comments about documentation, data modeling and the use of Erwin 4.0. In this first part we will not concentrate in process and class modeling (UML), but just to model a small database with customer, product and sales order maintenance. It will be a study-focused example for you to understand the process as a whole.

    Documentation

    This topic is the weak point of most developers, because as we have all the process in our minds, we leave documentation in a secondary place and, you can be sure, we are always hurt by this.

    There are several arguments I could use to try convince you about documenting your system right, among them: easier project comprehension by yourself or by another developer, process clarity, etc.

    But I confess that the argument that convinced me about the need of documenting was: "If you don't document your project you will lose time, and mainly, money".

    Let me explain: When you develop a project for a customer and you don't detail what you'll do, you don't have any control about what you already did and what's pending, and you'll never finish this project. Your customer will always find a "little thing missing". Certainly, after everything is working, you have already heard something like this:

    Where is the module that integrates that with the accounting and payment systems?

    If you did a proper documentation (and it is signed by your customer) it is enough to show them that this module wasn't included in the project and if they want to build it, it would be another project, with its own negotiation over values, timeframes, etc.

    Have you seen how you lose time and money if you don't document your working process? I hope that this argument works for you also.

    The data modeling

    When I was studying some theories about the modeling process with another developer he told me something that maybe most of us feel: "I would need to learn something I don't know to make something I already know how to do".

    Actually, data modeling has the goal of creating the database with its tables, fields and relationships, concepts that we all already know pretty well by practice. So, why should I do data modeling, why I don't just open SQL Server and create the tables I need for once?

    If the argument about documentation convinced you, you already have an answer for this question, but in the case you are not convinced yet, try this: with the data model I can create the same data structure in another

  • database, like Oracle, MySQL, SyBase, etc. That means I won't need to start creating tables if my customer decides some day to switch platforms or if I want to take advantage of the same database for another customer.

    The modeling process

    If you read a book about data modeling you will see that everything, or almost everything written there you already do on your daily work. The modeling can be divided in three big parts:

    Conceptual model: In this phase a general gathering is made about the customer needs. Here you shouldn't worry about how you'll solve it in your database, nor the tools to use. Just concentrate in understanding the process you'll have to automate, talk with the future users, learn the working of each company's (customer) division, take notes and follow the production processes. After that you'll have a prototype of what needs to be developed. The main error in this phase is to start thinking about the tables that you'll create, and how many "IFS" or "CASES" the system will have. Forget about this for now.

    Logical model: Now things start to have a more logical shape for us developers. Here we will specify which entities will be part of our data model. For our example those entities will be: CLIENTE (customer), PRODUTO (product), PEDIDO (order) and ITEM (it's a good practice to name always entities in singular) and what relationships exist between them: "CUSTOMER puts ORDER" and "PRODUCT forms ORDER", or if you prefer "CUSTOMER buys PRODUCT trough ORDER". In this model we will already define the main attributes of those entities, for instance: CUSTOMER has: Code, name, address and phone. PRODUCT has Code, name, price and barcode, etc. Here you detail how things happen; don't worry about how data will be stored, as theoretically you don't even know yet in which database would be used. Leave the "IFS" and "CASES" for later.

    Physical model: Here you will finally define what entities and relationships will be transformed into tables, what fields will be considered keys, the size and type of each field, what fields participate in the tables' relationships, and so on. Here we are almost "at home". You learned something you didn't know to come to something you already knew.

    You can be asking yourself: Do I have to make all this separately? The answer: no. Erwin will make a good part of the job, as after defining the logical model, the physical will be automatically generated, but from the conceptual model you can't escape, as they have not invented yet an application that substitutes the human skills for analyzing real world situations.

    The Erwin 4.0 tool

    It is not in the scope of this article to show all the features of this tool, as this would surely take a lot of text pages. We will just work with the needed for our database to be modeled. But anyway, there is a tutorial that explains very well the workings of Erwin. When opening Erwin, go to the HELP menu, TUTORIAL option...

  • ErWin Tutorial

    Let's do a quick "tour" trough this tool to identify some basic items:

    Look at the details circled in red

    When we click NEW, a dialog opens asking about the major details about the model we will go create. Select the logical/physical model and the SQL Server 2000 database, click Ok, and you'll open a totally blank work area.

  • These are basically the tools we will need now

    The "A" button is used to create our entities; the "B" set is used to define our relationships.

    In this case the possible relationships are, respectively: 1 -> N with identified relationship, N -> N and 1 -> N with unidentified relationship. Notice that the figure is formed by a line and a dot in one of the ends; the dot means the "N" side of the relationship, and in the case of an N -> N relationship there is a dot in each end. When we see this in practice, it would be easier to understand.

    The "C" combobox indicates that we are working in the logical model. Later we will switch this combo to work with the physical data model.

    Click on the "Entity" tool and click everywhere in the work area

    Type the name of the entity and press Enter

    Here we have a detail important enough

    Notice that "attribute_name" is located in the entity as if it was a subtitle. That means that this attribute will indicate our future table primary key.

    It is good to point that we don't know yet "where" we will go to store our data; we're just defining "how" they will be stored. We wouldn't worry about the field name, type (character, numeric, date, etc) or size. We can use composite names without any problem.

    Now we have to press "TAB", and thus we will go to the lower part of the entity where we will place the remaining attributes. When you press "ENTER" again you will go to place another attribute that will make part of the primary key of the future table (you'll have in this case a composite key).

    As this is not our case now, let's press "TAB".

    Now let's enter the remaining attributes, always pressing "ENTER" to move to the next line.

    We can now complete our model with the remaining entities.

    See how it will look

    Look at the "Attribute_name"

  • These will be our entities (don't hesitate: save your file!)

    The entities' relationships

    After defining our entities and their attributes we will relate each one with the others, following our system's logic.

    Let's define that:

    A CLIENTE (customer) can put at least a minimum of zero (0) PEDIDOS (orders) and several PEDIDOS as maximum.

    A PEDIDO (order) will have as a minimum zero (0) ITEM and at maximum several ITEM.

    An ITEM should have at least 1 PRODUTO (product) but a PRODUTO can participate in several ITEM of several PEDIDOS.

    Notice that it is not needed to have a PEDIDO (order) for you to be able to enter a CLIENTE (customer), but for you to put a PEDIDO, you need at least a PRODUTO entered.

    Let's relate CLIENTE and PEDIDO. Click the tool for unidentified relationship (the one with the dashed line and a final dot); click the CLIENTE entity and then the PEDIDO entity. See how it ends:

    Look at the icon and the created relationship

    Get another relationship with dashed lines

  • Let's relate PRODUTO with ITEM.

    We will use the same option:

    Notice that the filled dot at the end of the line always indicates the "N" side of the relationship and the empty dot indicates a non dependency on the "1" side.

    Most surely you already figured that the other icon will use a continuous line and that it represents a mandatory relationship.

    And that way we will relate now PRODUTO with ITEM:

    Look that now the line is continuous and it also have an indication on the "N" side

    Final considerations about the Logical Model

    Notice that when creating relationships, the key field of entity "1" is brought to the "N" entity, and when the relation is unidentified, this field is placed as a normal attribute, but when we use an identified relation (PRODUTO X ITEM) the key on

  • side "1" will make part of the other entity identifier.

    Notice also that the ITEM entity suffered and alteration on its format, now that its corners are rounded, indicating that it belongs to an "N -> N" relationship between PEDIDO and PRODUTO (many orders have many products).

    Our logical model is created. In the next article we'll transform this model into the physical one, and we'll talk about domains, database creation and reverse engineering.

    A VFP/SQL SERVER application from start to end - Part II

    Frederico Tomazetti

    In the first article, we developed a small logical model, using the modelling tool ErWin 4.0, and I explained about documentation and the process of data modelling.

    We will now transform this logical model into a physical model, and generate a database in SQL Server. We will define "how" our data is saved, what data types we will use for each field in our tables, which relationships will have cascading constraints, etc.

    Domains

    This is a resource that will help us a lot maintaining our database, since, through the definition of domains, we can create customized data types, and, when we change a domain, all related fields will inherit this change. For example: we detect that there are several fields, in different tables, that store monetary values. In this case, we can create a domain called MonetaryValue, and assign it to type "Money", with 10 digits, including 2 decimals. We will attribute this domain to all fields that need to save monetary values, and if, in the future, it becomes necessary to change these fields to work with 3 digits, we only have to change our domain, and all fields that depend on it will inherit this information.

    That's all; now, you don't need to review your entire database, for tables that have this field, to make this change one field at a time.

    Those who are accustomed to work with VFP tables will note that the range of options for data types becomes much larger; see below, in the two figures, the differences of data types between native VFP and SQL Server:

    Figure 2: Data types used by SQL Server

    Figure 1: Native VFP Data Types

  • The Physical Model

    Passing from the logical model to the physical model is fairly simple; you only have to change the LOGICAL combobox to PHYSICAL, or use the shortcut keys Ctrl+downarrow. To go back to the logical model, change the combobox, or use Ctrl-uparrow.

    Figure 3: View of our physical model

    Note that the modelling tool defined our data type with a template type. We will now define our domain and assign this domain to the fields; we will also rename the fields, so that there is a logical relationship between the fieldname and the name of the table.

    There are several ways to do this, and I believe that every one will prefer one method or the other. Personally, I will define the fieldnames in the manner which I find most practical, but you can use your own way without any problem.

  • Analyzing the data which will be saved, we note that all tables have an identifier, CLIENT and PRODUCT need character data type, and we will also need numerical and date fields.

    We will create the following domains:

    Domain Value

    Identifier INT

    String01 VarChar(60)

    String02 VarChar(30)

    Quantity Number(10,1)

    MonetaryValue Money(10,2)

    BarCode01 VarChar(13)

    Date DateTime

    Let's continue...

    Make sure that sorting is hierarchically (Sort -> Hierarchialy).

    Click on New ...

    Figure 5: Data of the new Domain

  • Figure 6: As the next step, define the data type

  • Two points are of interest here:

    The type VarChar stores only the data that is typed by the user, that is, you define the size as 60 characters, and this will be the maximum field size. But if the user types 10 characters, only 10 characters are stored, which saves us 50 characters.

    Figure 8: All the domains are defined

  • If we extrapolate this to a database that has hundreds of tables and millions of records, the space saving can be considerable.

    The CHAR type is equivalent to the CHARACTER type in Visual FoxPro. This type doesn't save unused space, that is, if you define char(60), all records of your table will have 60 spaces, even if the user only types 10 characters.

    The other interesting point is the possibility of allowing, or not, null values in the fields. Since we will use this domain to define the client and product name, we should not allow null data. Therefore, select the corresponding option as NOT NULL, as shown in the figure.

    Using these resources, define all the mentioned data types. In figure 8, you can see how our data types are defined.

    If this process isn't yet clear for you, investigate a little more about domains; they are a very useful resource, and one that makes life for the developer easier. You will find, at the end of this article, a link to download the modelling file that was used as an example for this article.

    Let's go one step further...

    We will define the names of the table fields. This can be done already in the logical model; I didn't do this, so that the idea of a Logical Model remains well separated from the physical model (LOGICAL = what to save. PHYSICAL = how to save it). At this point, these concepts should already be well defined, so that things start to look more homogeneous.

    Since we are in this process, let's define the domains for each field. See how it is done:

    Figure 9: Double-click on the CLIENT table

  • Figure 11: Click on the GENERAL tab and select the domain IDENTIFIER

    Continue defining the remaining fields as follows:

    Field Name Domain

    Client_Name CliName String01

    Client_Telephone CliTelephone String02

    Client_Adress CliAdress String01

    The table CLIENT will look like this:

  • Define the remaining tables to match the following configuration:

    Creating the Database

    To create our database directly from the modelling tools, you have to have a conection to SQL Server. If you want to create a file with the commands to create the database (a script) and then connect to the server and create the database, this is also possible. Let's investigate this second option. Although it is a little more work, you will have a better overview of the entire process:

    If you have a conection with the database server, you will have, in your computer, a tool called QUERY ANALYZER. Execute this application. It will ask for the name of the server and a password. Provide this information, using, if possible, the SQL Server administrator account, and execute the following command:

    Figure 14: After writing the command, type F5.

    Now, let's go back to ErWin, and generate the script file to create our database. Go to the TOOLS menu and select the

    option Forward Engineer/Schema generation ...

  • Figure 15: Click on Preview ...

    Figure 16: On the next form, click on the button to save

    I saved the file as UTMAG.SQL. It is attached to this article, together with the updated modelling file.

    We now have to execute the script through the Query Analyzer. Go back to this tool, and open the file that you just created. The Query Analyzer will ask you to save the command that you used previously to create the database. It isn't necessary to save this command.

  • An important detail, before you execute the script UTMAG.SQL, is to make sure that the database that you just created is in use. For this purpose, there is a combobox that shows which database is in use; normally, the MASTER database is open, as a templated. Change this combobox to our database (UTMAG).

    Figure 17: The file is open, check the detail in the combobox, and press F5

    Below, you can see if there was any error executing the script commands.

    Reverse Engineering

    The process of reverse engineering consists in importing, into our modelling tool, an existing database. This assists us in analyzing existing structures, and in correcting our own structure. Observe the reverse engineering options in ErWin, mainly the options of comparing an existing database and the current model. The next screenshot shows the comparison between the database we just created and our model. Since they are exactly

  • equal, there isn't much to do at this point.

    In the next article, we will create a table with SQL Server, and, through this tool, import it into our modelling tool. We will learn a little about the tool Enterprise Manager, and establish the conection between Visual FoxPro and SQL Server.

    In the second article, we created the domains, the physical model and the database. Now, we will create a process that will help us learn a little about the Enterprise Manager, and about reverse engineering, between SQL Server and Erwin. We will create a table in SQL Server and transfer it into Erwin.

    We could do the opposite, that is, create a new entity in Erwin, and transfer it into our existing database. I will show the first situation, since it is the most common one, when you take an existing database to analyze and maintain it.

    When I design a database, usually I create a table specifically to control the sequences of primary keys. I use a unique sequence for all tables; I don't like to use the auto-numbering feature of SQL Server. Please note that this is a personal preference, and I don't wish to claim that this is the best, or the worst, way to work. The advantage that I see in this system is that there will never be a repeated value in a primary key, and as we know, the primary key should not be shown to the user of the system.

    When the user asks me to have a code for a client, or one to number an order, I create a counter specifically for that table, using the auto-numbering system, but this is only for the user of the system; the real keys which relate the tables are sequences, and not available for the end-user. This is safer.

    The Enterprise Manager

  • Figure 1: This is the Enterprise Manager

    Note our tables in folder "Tablas" (tables).

    We will create a table called COUNTER that has a field called UniqueId of type "Identifier". This is quite simple: click on folder tables, and then on the icon "NEW".

  • We will create a stored procedure that does the counting "plus one" for our table. This could be done through VFP code, but we can also assign this function to the database, that is, start applying a little of the "client/server" way of working.

    Figure 5: The process is similar to creating the table:

  • Click on the folder Stored Procedure and click on the icon "NEW". In the dialog box, write the code below:

    CREATE PROCEDURE ObtainNewId AS DECLARE @nnId int

    BEGIN TRANSACTION SELECT @nnId = UniqueID FROM counter (HOLDLOCK) IF NOT (@@rowcount = 1) BEGIN INSERT INTO counter (UniqueID) values (1) select @nnId = 1 END

    UPDATE counter SET UniqueId = @nnId + 1 COMMIT TRANSACTION

    SELECT @nnId as UniqueId GO

    Figure 6: Check the syntax clicking on "Check syntax", and click OK to confirm.

  • To verify whether the counter is really working, open the query analyzer and execute this command in our database:

    EXEC ObtainNewId

    Transferring the changes to our model

    See, now, how our screen, that compares the database with the model (last screenshot from the previous article)

    looks now:

  • To transfer our changes to the model, click on the line that shows a difference, and click on the "Import" button. Note that a yellow arrow will indicate what will be imported.Click on the "Next" button and ...

    Figure 8: Click on "Start Import" ...

    Figure 9: Here is the procedure for the counter and...

    Figure 10: ... the table COUNTER

    Figure 11: Observe the window of the UDL file, and indicate the conection type.

  • Talking with Visual FoxPro

    If you have always worked only with Visual FoxPro, what you saw here is quite new, since VFP has its own database that doesn't require this sort of stuff.

    However, we are working with another database, much more robust than the VFP database, and the techniques studied here will be useful to work with any other database.

    For Visual FoxPro to be able to "talk" with SQL Server, several methods exist, such as ODBC/remote view, OLEDB/ADO, and others. For this article, I will use OLEDB/ADO.

    The connection between the application and the database is

    done through a connection string. To obtain this string quickly, there is a little "trick" that consists in creating a TXT file in any directory of your disk, renaming this file with a UDL extension, and executing the file.

    That's all, now confirm the data and edit the UDL file with Windows Notepad, where you will find your conection string ready for use. Finally, open Visual FoxPro and create a PRG with the following code:

    LOCAL lcStringCon as string, oCon as Custom

    lcStringCon = "Provider=SQLOLEDB.1;" +; "Password=utmag;" +; "Persist Security Info=True;" +; "User ID=ftomazetti;" +; "Initial Catalog=UTMAG;" +; "Data Source=servidor" oCon = CREATEOBJECT("adodb.connection") oCon.open(lcStringCon)

    Obviously, you must use the connection string generated by your UDL file, which has the correct password for your database.

    You created an object called oCon that has a connection with the database.

  • There are other ways to connect with a database, which don't require hard-coding the password. For instance, you can create a form where the user types the password, or you can use the connection which comes with Windows; in this case, all machines involved have to have Windows 2000 (Server or Professional) or Windows NT installed.

    The integrated connection is, in my opinion, the best way to work, although it has the inconvenience that it doesn't work with Windows 98 or Windows ME.

    In the next article, we will see some data manipulation functions with OLEDB/ADO, and we will create our connection objects and the access to the database.

    In this article things can seem a bit confusing, because I will focus in some basic concepts about access to SQL Server using OLEDB/ADO. To that purpose, I will show some functions and comment on every one of them.

    These functions will be used later in the article sequence, being the basis for the upcoming "middle tier" component of a Client/Server system.

    Beginning

    To access a database different from the VFP native one, we need a connection string, as we saw at the end of the third article, as we generally need to configure the server name, database name, login type, etc.

    To that purpose we will use an INI file that is just a text file (made with the note pad) that has some parameters, and the routine bellow to read them.

    The file UTMAG.INI has these lines:

    [UTMAG] servidor=SRVUTMAG banco=UTMAG autolog=S The code bellow will be on the system's main PRG. This code creates properties for _SCREEN that are kept visible throughout the system, avoiding public variable creation.

    This way we can switch the server or database name without the need to recompile our system; it's just a matter of editing the INI file.

    The "Autolog" option will determine later on if we will use the Windows integrated login. This option is available if you're using Windows NT (Windows 2000) on the server (2000 server) as well as on terminals (2000 professional) as this version has a system login integrated with other applications. If you have Windows 95, 98 or Millennium, you will have to set "AUTOLOG=N" and during the system initialization the user will be asked to enter his login name and password for SQL Server. We will see this later on.

    This is the code to read data from UTMAG.INI:

    _Screen.AddProperty("servidor",'') _Screen.AddProperty("banco",'') _Screen.AddProperty("integrado",'') _Screen.AddProperty("usuario",'')

    * Search for the data on UTMAG.INI - parameters * 1 - Sections on square brackets [ARQUIVO] * 2 - Variable into this section * 3 - default value if not found * 4 - variable that will receive the value (passed by reference) * 5 - character length that the variable will receive * 6 - .ini file

  • Declare Integer GetPrivateProfileString In Win32API As GetPrivStr ; STRING cSection ,; STRING cKey ,; STRING cDefault ,; STRING @cBuffer ,; INTEGER nBufferSize ,; STRING cINIFile lcServidor = Space(20) lcBanco = Space(20) lcIntegrado = Space(10)

    GetPrivStr('dt_sistemas', 'servidor' ,'vazio', @lcServidor , 20, 'outros\dt.ini') GetPrivStr('dt_sistemas', 'banco' ,'vazio', @lcBanco , 20, 'outros\dt.ini') GetPrivStr('dt_sistemas', 'autolog' ,'vazio', @lcIntegrado , 10, 'outros\dt.ini')

    _Screen.servidor = Left(lcServidor, Len(Alltrim(lcServidor)) - 1) _Screen.banco = Left(lcBanco, Len(Alltrim(lcBanco)) - 1) _Screen.integrado= Left(lcIntegrado, Len(Alltrim(lcIntegrado)) - 1) The small code snippet below stores the currently logged user name. This information will be used later to confirm integrated login. lcNome = Sys(0) lnPosicao = At('#',lcNome) + 2 lnTamanho =Len(lcNome) - At('#',lcNome) -1 _Screen.usuario = Substr(lcNome,lnPosicao,lnTamanho)

    Access to the database and business rules

    The code bellow will create our data access class, which will considerably grow during the system development. I will show just the basics to avoid complicating things by now, because when you start transforming your system to Client/Server you will obviously use your existing structure.

    Define Class UtmagDados As Relation OlePublic

    lors = Null loConn = Null cMensError = '' nNativeError = 0

    Protected cStringSql,; cServerName,; cDataBaseName,; cIntegrado ,; cUserID,; cPwd,cConnectionString,; cCampos,cValor,cCamposSimples,cSqlWhere

    Procedure Init ( pServidor As String, ; pBanco As String, ; pIntegrado As String, ; pUsuario As String, ; pSenha As String )

    With This .cIntegrado = Alltrim(pIntegrado) .DefineAtributoConexao(pServidor, pBanco, pUsuario, pSenha) .cStringSql = "" .cConnectionString = "" .loConn = Null .lors = Null Endwith Endproc Procedure Error Lparameters nError, cMethod, nLine If This.loConn.Errors.Count>0

  • This.cMensError = "Numero do Error: " + ; Transform(This.loConn.Errors.Item(0).NativeError) + Chr(13) + ; "Descricao Error: "+This.loConn.Errors.Item(0).Description + Chr(13) +; "Origem do Error: "+This.loConn.Errors.Item(0).Source Else This.cMensError = "Numero do Error: "+Transform(nError) + Chr(13) +; "Mensagem do Error: "+Message( ) + Chr(13) +; "Linha do Error: "+Transform(nLine) + Chr(13) +; "Metodo: "+cMethod Endif Endproc

    Procedure Destroy With This If Vartype(.loConn) = "O" .Desconectar() Endif Endwith Endproc

    *************************************************************

    * Procedure: DefineAtributoConexao * Objetivo: Initializes the attributes to connect to the database * That data actually comes from a public object having * the data to connect to the DB *************************************************************

    Procedure DefineAtributoConexao ( ; pServidor As String, ; pBanco As String, ; pUsuario As String, ; pSenha As String )

    With This .cServerName = Alltrim(pServidor) .cDataBaseName = Alltrim(pBanco) .cUserID = Alltrim(pUsuario) .cPwd = Alltrim(pSenha) Endwith Endproc The procedure bellow makes the connection with the database. Notice the condition: IF .cIntegrado = 'S' - Here we will use or not the integrated Windows login. *************************************************************

    * Procedure: Conectar * Objetivo: Makes the connection with the database trough ADO *************************************************************

    Procedure Conectar (pConnectionString As String ) As Boolean Local llOK With This If Type("pConnectionString") = "C" *-- The connection string came as a parameter .cConnectionString = pConnectionString Else If .cIntegrado = 'S' * The connection string comes from Windows 2000 login .cConnectionString = 'Provider=SQLOLEDB.1' +; ';Integrated Security=SSPI' +; ';Persist Security Info=False' +; ';Initial Catalog=' + .cDataBaseName +; ';Data Source=' + .cServerName Else * string para conexo de segurana mista * exige usurio digitar login e senha .cConnectionString = 'Provider=SQLOLEDB.1' +; ';Data Source=' + .cServerName +; ';trusted_connection=false;' +; ';Initial Catalog=' + .cDataBaseName +;

  • ';User ID=' + .cUserID +; ';PassWord=' + .cPwd Endif Endif .loConn = Createobject("adodb.connection") If Vartype(.loConn) = "O" .loConn.Open(.cConnectionString) If .loConn.State = 1 && Conexao Aberta llOK = .T. Else llOK = .F. Endif Endif Endwith Return llOK Endproc This procedure disconnects the system from the database: *************************************************************

    * Procedure: Desconectar * Objetivo: Closes the ADO connection to the database *************************************************************

    Procedure Desconectar With This If Vartype(.lors) = "O" If .lors.State # 0 .lors.Close() Endif Endif If Vartype(.loConn) = "O" If .loConn.State # 0 .loConn.Close() Endif Endif .loConn = Null .lors = Null Endwith Endproc The following two procedures create and close the database connection, as it is not needed to work permanently connected. In this way we can maximize database access. Imagine a terminal that's used just every 30 minutes. There is no need to have a permanent connection between this terminal and the database.

    It is an economic issue, as your customer purchase an X amount of database access licenses, and you can only use this amount. This way we can multiply the access level with the same number of licenses.

    *************************************************************

    * Procedure: CriaConexao * Objetivo: Creates a public connection that would be shared * by all forms. **************************************************************

    Procedure CriaConexao _Screen.omanipuladados = Createobject("ManipulaDados", _Screen.servidor,; _screen.banco,; _screen.integrado,; _screen.usuario,; _screen.senha) *-- Estabeleca a conexao com o BD _Screen.omanipuladados.conectar() Endproc

    *************************************************************

    * Procedure: FechaConexao * Objetivo: Closes a public connection **************************************************************

    Procedure FechaConexao If Vartype(_Screen.omanipuladados) = "O"

  • _Screen.omanipuladados.Desconectar() _Screen.omanipuladados = Null Endif Endproc The following three procedures will be used for transactions. Who already used a DBC on Fox would be already familiar with transactions (BEGIN TRANSACTION, END TRANSACTION, ROLLBACK, etc). *************************************************************

    * Procedure: IniciarTransacao * Objetivo: Opens a transaction trough ADO *************************************************************

    Procedure IniciarTransacao This.loConn.BeginTrans Endproc *************************************************************

    * Procedure: EncerrarTransacao * Objetivo: Commits a transaction trough ADO *************************************************************

    Procedure EncerrarTransacao This.loConn.CommitTrans Endproc *************************************************************

    * Procedure: AbortarTransacao * Objetivo: Rolls back a transaction trough ADO *************************************************************

    Procedure AbortarTransacao This.loConn.RollBackTrans Endproc These las two procedures will be used to execute a Stored Procedure that returns a sequential counter from our CONTADOR table on the database. *************************************************************

    * Procedure: ObterNovoContador * Objetivo: Returns a unique counter according to a stored procedure *************************************************************

    Procedure ObterNovoContador As Integer *-- Obtem o ID_UNICO do Sistema Return This.executarSP("obternovooid",0,"",1,"@nRetorno") Endproc

    *************************************************************

    * Procedure: ExecutarSP * Objetivo: Function to execute database stored procedures * Parametros: pNomeDaSP - Store procedure name to execute * pNroParEnt - input parameter counter * pParEntrada - output parameters (beginning with @ and comma-separated) * pNroParSai - output parameter counter - can be just 1 or 0 * pParSaida - output parameter beginning with @ *************************************************************

    Procedure executarSP As Custom Parameters pNomeDaSP, pNroParEnt, pParEntrada, pNroParSai, pParSaida * declarao de variveis locais Local loADOCmd, loADOParam && objetos

    Local adInteger, adCurrency, adDate ,; && uso do ADO adBoolean, adChar, adNumeric, adVarChar, AdParamInput ,; adParamOutPut, adCmdStoredProc, AdExecuteNoRecords

    Local lnTamanhoString, laEntradas, laSaidas, lnElementoMatriz, ; lcGuardarString && uso interno da funo Local lnRetorno,i,llConectou

    * valores utilizado pelo ADO adInteger = 3 adCurrency = 6 adDate = 7 adBoolean = 11 adChar = 129

  • adNumeric = 131 adVarChar = 200 AdParamInput = 1 adParamOutPut = 2 adCmdStoredProc = 4 AdExecuteNoRecords = 128 If Vartype(This.loConn) # "O" *-- Se nao houver conexao estabelece a conexao If !This.Conectar() Return .F. Else llConectou = .T. Endif Endif loADOCmd = Createobject("ADODB.Command") loADOCmd.ActiveConnection = This.cConnectionString

    loADOCmd.CommandText = pNomeDaSP loADOCmd.CommandType = adCmdStoredProc

    * criar parametros de entrada If pNroParEnt > 0 && monta um array com os parametros de entrada lnTamanhoString = Len(pParEntrada) Dimension laEntradas(pNroParEnt) lcGuardarString = '' lnElementoMatriz = 1 For i = 1 To lnTamanhoString If Substr(pParEntrada,i,1) = ',' laEntradas(lnElementoMatriz) = lcGuardarString lnElementoMatriz = lnElementoMatriz + 1 lcGuardarString = '' Else lcGuardarString = lcGuardarString + Substr(pParEntrada,i,1) Endif Next laEntradas(lnElementoMatriz) = lcGuardarString

    For i = 1 To pNroParEnt loADOParam = loADOCmd.CreateParameter(laEntradas(i), adVarChar, AdParamInput) loADOCmd.Parameters.Append(loADOParam) Next Endif

    * criar parametros de sada (retorno da stored procedure) If pNroParSai > 0 lnTamanhoString = Len(pParSaida) && monta um array com os parametros de saida Dimension laSaidas( pNroParSai) lcGuardarString = '' lnElementoMatriz = 1 For i = 1 To lnTamanhoString If Substr(pParSaida,i,1) = ',' laSaidas(lnElementoMatriz) = lcGuardarString lnElementoMatriz = lnElementoMatriz + 1 lcGuardarString = '' Else lcGuardarString = lcGuardarString + Substr(pParSaida,i,1) Endif Next laSaidas(lnElementoMatriz) = lcGuardarString

    For i = 1 To pNroParSai loADOParam = loADOCmd.CreateParameter(pParSaida, adInteger, adParamOutPut) loADOCmd.Parameters.Append(loADOParam) Next Endif loADOCmd.Execute(,,AdExecuteNoRecords) lnRetorno = loADOCmd.Parameters(pParSaida).Value

  • loADOCmd = Null If llConectou This.Desconectar() Endif

    If pNroParSai = 1 Return lnRetorno Else Return -1 Endif Endproc EndDefine Things may seem a bit out of place at this moment, but these procedures (and some less relevant ones that we will see on our work sequence) are the ones that make all the data access and manipulation work.

    It is fundamental to learn ADO. For that I recommend the articles on MSDN on the subject and a book that taught me a lot called "Dominando SQL SERVER 2000 - A Biblia".

    Another book that can really help, specially for the fifth part of this article, is "Desenvolvendo solues XML com VISUAL FOXPRO" from our colleague Fbio Vazquez.

    (Note: Both are Portuguese editions.)

    Finally I wish to thank Breno Viana, a fellow UT member and coworker of mine, as many of the things in the functions presented come from him.

    The fifth part of this article is dedicated to the study of ADO within Visual FoxPro. The functions I will now present are a continuation of those presented in part IV. They are functions of vital importance for the future of the project; a failure at this point will endanger the entire system.

    When I started the project "A VFP/SQL Server application from start to end", VFP 8.0 was not yet officialy launched (it was in the beta version), therefore, the functions presented here were developed, tested, and are being used, in version 7.0.

    We know that in version 8.0 there are ready-made functions that make life easier for the developer who wants to work with Visual FoxPro and SQL Server.

    Executing Transact/Sql Commands

    The function below executes a SQL statement in the database.

    The main point is the parameters received by the function. The first "pStringSql" is the string that will be executed, something like: "Select * from clientes".

    The second parameter (pCursorLocation) indicates on which side the cursor will be created, the client or the server. This has a great relevance on the performance of your system and the traffic it causes on the network.

    We can't claim that the client-side cursor is better than the server-side cursor, or the other way round; rather, this depends on the structure of our system, and on whether the user user has an overloaded server, and on whether the client machines have fast or slow processors. That is, external factors will influence this type of decision.

    In my case, I use client-side cursors when the amount of data returned isn't very great, and server-side cursor for a major amount of data, or when executing an INSERT, UPDATE or DELETE on a table.

  • The third parameter (pCursorType) indicates how the cursor is opened, and has 4 options (values from 0 to 3). The most common option is the value zero (default), which defines the cursor as being opened "forward only". If you use option 3, for instance, you can only read data. Apparently, option 2 (adOpenDynamic) would be the best, but this consumes processing power, and we only need to read data sequentially.

    The fourth parameter (pLockType) indicates the type of locking that ADO executes on the record or table of the database. This process is practically the same as the one used natively in Visual FoxPro tables.

    Normally I use the standard locking option (1 - adLockReadOnly), since it is safer than the others. *************************************************************

    * Procedure: Execute * Purpose: Execute a SQL command in the database, through ADO * Parmetros: pStringSQL - string that will be executed in the database * pCursorLocation - Cursor location * 2 = adUseServer (Default) * 3 = adUseClient * pCursorType - How the cursor will be opened * 0 = adOpenForwardOnly (Default) * 1 = adOpenKeyset * 2 = adOpenDynamic * 3 = adOpenStatic * pLocktype - Locking type * 1 = adLockReadOnly (Default) * 2 = adLockPessimistic * 3 = adLockOptimistic * 4 = adLockBatchOptimistic * Return value: True or False *************************************************************

    Procedure Executar (pStringSql As String,; pCursorLocation As Integer,; pCursorType As Integer,; pLocktype As Integer) As Boolean

    This part of the function verifies data typing and, in case some parameter wasn't specified, it assumes a default value for it.

    With This If Vartype(pStringSql) "C" Return .F. Endif If Vartype(.loConn) # "O" *-- If there is no conection, establish the conection If !.Conectar() Return .F. Endif Endif

    *-- RS location type If Vartype(pCursorLocation) "N" pCursorLocation = 2 Else If !(pCursorLocation >= 2 And pCursorLocation = 0 And pCursorType

  • Endif Endif

    *-- Locking type If Vartype(pLocktype) "N" pLocktype = 1 Else If !(pLocktype >= 1 And pLocktype

  • If Vartype(.lors) = "O" If .lors.State = 1 .lors.Save (pNome,pTipoADO) llOK = .T. Endif Endif Endwith Return llOK Endproc

    Creating a local cursor

    To create a local Visual FoxPro cursor with the data we received from SQL Server, we have to use XML. See the function below, which uses the two functions explained above.

    The function receives a string (pSql), a name for a temporary file - local cursor (pArqTmp), the cursor type (pTipoCursor) and whether this cursor will receive a blank record after being created (very useful for data entry through grids).

    Here we should emphasize that it is necessary to have service pack 1 of Visual FoxPro 7 (available as a download from the Microsoft site), since this service pack corrects a problem with the XMLTOCURSOR command.

    *************************************************************

    * Procedure: CriaCursor (create cursor) * Purpose: Create a local VFP cursor through an ADO RecordSet * Parameters: pSql = SQL command to execute * pArqTMP = Name of TMP file created * pTipoCursor = Flag that identifies the type of cursor created * (.T. = 512 , .F. = 512 + 8192) * pInsereRegistro = Insert a blanc record after creating the cursor *************************************************************

    parameters pSql,; pArqTMP,; pTipoCursor,; pInsereRegistro Local llRetorno Local lcXML If _Screen.omanipuladados.Executar(pSql) lcXML = "temp\"+Sys(2015)+".xml" If _Screen.omanipuladados.SalvarRS(1,lcXML) Xmltocursor(lcXML,pArqTMP,Iif(!pTipoCursor,512 + 8192,512)) Delete File &lcXML If !pInsereRegistro If Reccount() = 0 Append Blank Endif Endif llRetorno = .T. Endif Else =RotinaDeErro (_Screen.omanipuladados.cMensError + Chr(13)+Chr(13) ; +"Failure calling view!" ,'', _Screen.omanipuladados.nNativeError, .T.) Endif Return llRetorno

    In this sixth and last article we will create the forms to save data, create local cursors to query, etc.

    Since this moment, you should be able to adapt the data access and data query classes that you already have built and in use from many time ago. To this purpose, I'll create the forms in the most simple way I can. I will avoid using a form class and I will leave the method code for doing queries and everything else highly visible.

  • To keep all as didactic as possible, I didn't placed complex data validation rules. Take a look at the code inside each form and use the ideas there to the classes the you are using already in your daily work.

    You will need to build the example with the code included in the previous articles, as the forms provided in this one makes reference to the previously presentes functions.

    Some important issues that you have to keep in mind when adapting your own classes which are accessing free tables today, using VFP native data, are:

    1. You are developing an application which will be able to work with huge data volumes. 2. Many users will be able to access, update or insert data, and they will be able to do this trough this VFP-

    built application or trough any other application written with .NET, VB, ASP, etc. 3. Don't use methods that bring all the data in a table, mainly if this table have the chance to have

    thousands of records. That means that you will have to avoid the infamous "NEXT", "PREVIOUS", "FIRST" and "LAST" buttons, as they work accessing data all the time.

    4. You should have to get accustomed to use transactions instead of LOCK/UNLOCK. 5. Create views on SQL Server to make easier report creation, as views are SQL instructions that work as

    tables, but are a lot faster than executing a complex SQL sentence trough ADO. 6. When the need arise to create a code block with INSERTs, UPDATEs or DELETEs for several records

    and different tables (for example, inserting a sales order and all its items), try to concatenate the strings with all the commands and send everything just once to the database, so you execute several related commands in a single database access.

    Bellow are some of the functions used inside the forms. These routines are fundamental for the process, so we will analyze them mor carefully:

    Properties used in the forms

    THISFORM.ACAO - Receives 1 for insert and 2 for update. THISFORM.ID_UNICO - Receives the record ID to process.

    The object _SCREEN.OMANIPULADADOS is instantiated when executing the application and have the ADO objects used to manipulate data.

    Code to insert or update data

    Quite simple. Given the action parameter, (THISFORM.ACAO) we send an insert or an update and execute the string at the database.

    LOCAL lcString as String

    SET TEXTMERGE on WITH _screen.omanipuladados

    IF thisform.acao = 1 && insert thisform.id_unico = .ObterNovoContador() TEXT TO lcString noshow INSERT INTO PRODUTO (ProdID, ProdNome, ProdPrecoUnit, ProdCodBarra) values ( , , , ) ENDTEXT ELSE && alterao TEXT TO lcString noshow

  • UPDATE PRODUTO set ProdNome = , ProdPrecoUnit = , ProdCodBarra = where CliID = ENDTEXT ENDIF * Execute the string on the database .IniciarTransacao() .executar(lcString,3) IF .lors.errorcount = 0 .EncerrarTransacao ELSE .AbortarTransacao ENDIF ENDWITH

    Code to search for data (look at the product maintenance form)

    In this routine we have a SELECT that searches for the product data. After executing that command we check if the recordset has any records. If it is zero, we tell that to the user and return FALSE.

    As the user is performing a search, we take the chance to switch the AO property to 2 and then we get the data from the recordset and load it to the form fields.

    LOCAL lcBusca as String. lcString as String

    lcBusca = Inputbox("Name:","Search")

    IF EMPTY(lcBusca) RETURN .f. ENDIF

    lcBusca = ALLTRIM(lcBusca) + "%"

    SET TEXTMERGE on TEXT TO lcString noshow SELECT ProdID, ProdNome, ProdPrecoUnit, ProdCodBarra from PRODUTO where ProdNome like endtext SET TEXTMERGE off WITH _screen.omanipuladados .executar(lcString,3) IF .lors.recordcount

  • Inserting data in a one to many relatioship is performed after all the sales order items are entered (on a grid). We do that with an insert for the PEDIDO table and then, in a SCAN...ENDSCAN we place the item table's INSERTs into the same variable. Notice that there is a blank line after and before each insert command; this is needed to leave a separation between the commands at execution time.

    Consider that the string is executed just at the end of the process. This way, if any problem arises during the data updating process (look at the line IF LORS.ERRORCOUNT = 0), the transaction will be rolled-back. If we save the data as the user is entering it, we will face the risk of leaving an open transaction for too much time, beside significatively increasing netwrok traffic.

    * Important - The lcString variable will receive several concatenations. * That's why there is a blank space after and before * each insert or update command (See TEXT / ENDTEXT) LOCAL lcString as String SET TEXTMERGE on WITH _screen.omanipuladados IF thisform.acao = 1 && inclusao thisform.id_unico = .ObterNovoContador() TEXT TO lcString noshow

    INSERT INTO PEDIDO ( PedID, PedData, PedTotal, CliID) values ( , , , )

    ENDTEXT

    * Itens do pedido SELECT itens GO top SCAN lnContador = .ObterNovoContador() TEXT TO lcString NOSHOW additive INSERT INTO ITEM (ItemID, ItemQuant, ItemValVenda, PedID, ProdID) values (, , , , )

    ENDTEXT ENDSCAN

    ELSE && update

    SELECT itens GO top SCAN

    TEXT TO lcString NOSHOW additive

    UPDATE ITEM SET ItemQuant = , ItemValVenda = where PedID = TRANSFORM(thisform.id_unico) and ProdID = TRANSFORM(itens.IdProduto)

    ENDTEXT

  • ENDSCAN

    ENDIF

    * Execute the string on the database .IniciarTransacao() .executar(lcString,3) IF .lors.errorcount = 0 .EncerrarTransacao ELSE .AbortarTransacao ENDIF ENDWITH

    Function to search sales order data

    I left this function incomplete on purpose, so if you analyze it deeper, you'll find that it returns the first sales order from a customer. In this case you will need to work with the return value of the first select so you give the user the chance to select which order to update.

    This routine is a bit long and place it now in this function could affect the idea of sowing a basic operation for search and save data.

    After that we have a SELECT statement that will search for the data on the database and trough a DO...WHILE loop over the recordset, we load them on a supporting cursor for the grid.

    LOCAL lcBusca as String. lcString as String, lnIdCliente

    * Buscar o cliente lcBusca = Inputbox("Customer name:","Search") IF EMPTY(lcBusca) RETURN .f. ENDIF

    lcBusca = ALLTRIM(lcBusca) + "%"

    SET TEXTMERGE on TEXT TO lcString noshow SELECT CliID from CLIENTE where CliNome like endtext SET TEXTMERGE off

    WITH _screen.omanipuladados .executar(lcString,3) IF .lors.recordcount

  • and PE.CliID = ENDTEXT

    .executar(lcString,3) IF .lors.recordcount