Mastering Microsoft Visual Basic 2010 phần 8 pptx

105 442 0
Mastering Microsoft Visual Basic 2010 phần 8 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

STORING DATA IN DATASETS 703 untyped DataSets. In the following chapter, I’ll discuss in detail typed DataSets and how to use them in building data-bound applications. The DataAdapter Class To use DataSets in your application, you must first create a DataAdapter object, which is the preferred technique for populating the DataSet. The DataAdapter is nothing more than a col- lection of Command objects that are needed to execute the various SQL statements against the database. As you recall from our previous discussion, we interact with the database by using four different command types: one to select the data and load them to the client computer with the help of a DataReader object (a Command object with the SELECT statement) and three more to submit to the database the new rows (a Command object with the INSERT statement), update existing rows (a Command object with the UPDATE statement), and delete existing rows (a Com- mand object with the DELETE statement). A DataAdapter is a container for Connection and Command objects. If you declare a SqlDataAdapter object with a statement like the following: Dim DA As New SqlDataAdapter you’ll see that it exposes the properties described in Table 16.1. Table 16.1: SqlDataAdapter object properties Property Description InsertCommand A Command object that’s executed to insert a new row UpdateCommand A Command object that’s executed to update a row DeleteCommand A Command object that’s executed to delete a row SelectCommand A Command object that’s executed to retrieve selected rows Each of these properties is an object and has itsownConnectionproperty,becauseeachmay not act on the same database (as unlikely as it may be). These properties also expose their own Parameters collection, which you must populate accordingly before executing a command. The DataAdapter class performs the two basic tasks of a data-driven application: It retrieves data from the database to populate a DataSet and submits the changes to the database. To populate a DataSet, use the Fill method, which fills a specific DataTable object. There’s one DataAdapter per DataTable object in the DataSet, and you must call the corresponding Fill method to populate each DataTable. To submit the changes to the database, use the Update method of the appropriate DataAdapter object. The Update method is overloaded, and you can use it to submit a single row to the database or all edited rows in a DataTable. The Update method uses the appropriate Command object to interact with the database. Passing Parameters Through the DataAdapter Let’s build a DataSet in our code to demonstrate the use of the DataAdapter objects. As with all the data objects mentioned in this chapter, you must add a reference to the System.Data namespace with the Imports statement. 704 CHAPTER 16 DEVELOPING DATA-DRIVEN APPLICATIONS Start by declaring a DataSet variable: Dim DS As New DataSet To access the classes discussed in this section, you must import the System.Data namespace in your module. Then create the various commands that will interact with the database: Dim cmdSelectCustomers As String = "SELECT * FROM Customers " & "WHERE Customers.Country=@country" Dim cmdDeleteCustomer As String = "DELETE Customers WHERE CustomerID=@CustomerID" Dim cmdEditCustomer As String = "UPDATE Customers " & "SET CustomerID = @CustomerID, CompanyName = @CompanyName, " & "ContactName = @ContactName, ContactTitle = @ContactTitle " & "WHERE CustomerID = @CustID" Dim cmdInsertCustomer As String = "INSERT Customers " & " (CustomerID, CompanyName, ContactName, ContactTitle) " & "VALUES(@CustomerID, @CompanyName, @ContactName, @ContactTitle) " You can also create stored procedures for the four basic operations and use their names in the place of the SQL statements. It’s actually a bit faster, and safer, to use stored procedures. I’ve included only a few columns in the examples to keep the statements reasonably short. The various commands use parameterized queries to interact with the database, and you must add the appropriate parameters to each Command object. After the SQL statements are in place, we can build the four Command properties of the DataAdapter object. Start by declaring a DataAdapter object: Dim DACustomers As New SqlDataAdapter() Because all Command properties of the DataAdapter object will act on the same database, you can create a Connection object and reuse it as needed: Dim CN As New SqlConnection(ConnString) The ConnString variable is a string with the proper connection string. Now we can create the four Command properties of the DACustomers DataAdapter object. Let’s start with the SelectCommand property of the DataAdapter object. The following state- ments create a new Command object based on the preceding SELECT statement and then set up a Parameter object for the @country parameter of the SELECT statement: DACustomers.SelectCommand = New SqlClient.SqlCommand(cmdSelectCustomers) DACustomers.SelectCommand.Connection = CN Dim param As New SqlParameter param.ParameterName = "@Country" param.SqlDbType = SqlDbType.VarChar param.Size = 15 param.Direction = ParameterDirection.Input param.IsNullable = False param.Value = "Germany" DACustomers.SelectCommand.Parameters.Add(param) STORING DATA IN DATASETS 705 This is the easier, if rather verbose, method of specifying a Parameter object. You are familiar with the Parameter object properties and already know how to configure and add parameters to a Command object via a single statement. As a reminder, an overloaded form of the Add method allows you to configure and attach a Parameter object to a Command object Parameters collection with a single, if lengthy, statement: DA.SelectCommand.Parameters.Add( New System.Data.SqlClient.qlParameter( paramName, paramType, paramSize, paramDirection, paramNullable, paramPrecision, paramScale, columnName, rowVersion, paramValue) The paramPrecsion and paramScale arguments apply to numeric parameters, and you can set them to 0 for string parameters. The paramNullable argument determines whether the parameter can assume a Null value. The columnName argument is the name of the table column to which the parameter will be matched. (You need this information for the INSERT and UPDATE commands.) The rowVersion argument determines which version of the field in the DataSet will be used — in other words, whether the DataAdapter will pass the current version (DataRowVersion.Current) or the original version (DataRowVersion.Original) of the field to the parameter object. The last argument, paramValue, is the parameter’s value. You can specify a value as we did in the SelectCommand example, or you can set this argument to Nothing and let the DataAdapter object assign the proper value to each parameter. (You’ll see in a moment how this argument is used with the INSERT and UPDATE commands.) Finally, you can open the connection to the database and then call the DataAdapter’s Fill method to populate a DataTable in the DataSet: CN.Open DACustomers.Fill(DS, "Customers") CN.Close The Fill method accepts as arguments a DataSet object and the name of the DataTable it will populate. The DACustomers DataAdapter is associated with a single DataTable and knows how to populate it, as well as how to submit the changes to the database. The DataTable name is arbitrary and need not match the name of the database table where the data originates. The four basic operations of the DataAdapter (which are none other than the four basic data-access operations of a client application) are also known as CRUD operations: Create/Retrieve/Update/Delete. The CommandBuilder Class Each DataAdapter object that you set up in your code is associated with a single SELECT query, which may select data from one or multiple joined tables. The INSERT/UPDATE/DELETE queries of the DataAdapter can submit data to a single table. So far, you’ve seen how to manually set up each Command object in a DataAdapter object. There’s a simpler method to specify the queries: You start with the SELECT statement, which selects data from a single table, and then let a CommandBuilder object infer the other three statements from the SELECT statement. Let’s see this technique in action. 706 CHAPTER 16 DEVELOPING DATA-DRIVEN APPLICATIONS Declare a new SqlCommandBuilder object by passing the name of the adapter for which you want to generate the statements: Dim CustomersCB As SqlCommandBuilder = New SqlCommandBuilder(DA) This statement is all it takes to generate the InsertCommand, UpdateCommand, and DeleteCommand objects of the DACustomers SqlDataAdapter object. When the compiler runs into the previous statement, it will generate the appropriate Command objects and attach them to the DACustomers SqlDataAdapter. Here are the SQL statements generated by the CommandBuilder object for the Products table of the Northwind database: UPDATE Command UPDATE [Products] SET [ProductName] = @p1, [CategoryID] = @p2, [UnitPrice] = @p3, [UnitsInStock] = @p4, [UnitsOnOrder] = @p5 WHERE (([ProductID] = @p6)) INSERT Command INSERT INTO [Products] ([ProductName], [CategoryID], [UnitPrice], [UnitsInStock], [UnitsOnOrder]) VALUES (@p1, @p2, @p3, @p4, @p5) DELETE Command DELETE FROM [Products] WHERE (([ProductID] = @p1)) These statements are based on the SELECT statement and are quite simple. You may notice that the UPDATE statement simply overrides the current values in the Products table. The CommandBuilder can generate a more elaborate statement that takes into consideration concurrency. It can generate a statement that compares the values read into the DataSet to the values stored in the database. If these values are different, which means that another user has edited the same row since the row was read into the DataSet, it doesn’t perform the update. To specify the type of UPDATE statement you want to create with the CommandBuilder object, set its ConflictOption property, whose value is a member of the ConflictOption enumeration: CompareAllSearchValues (compares the values of all columns specified in the SELECT statement), CompareRowVersion (compares the original and current versions of the row), and OverwriteChanges (simply overwrites the fields of the current row in the database). The OverwriteChanges option generates a simple statement that locates the row to be updated with its ID and overwrites the current field values unconditionally. If you set the ConflictOption property to CompareAllSearchValues, the CommandBuilder will generate the following UPDATE statement: UPDATE [Products] SET [ProductName] = @p1, [CategoryID] = @p2, [UnitPrice] = @p3, [UnitsInStock] = @p4, STORING DATA IN DATASETS 707 [UnitsOnOrder] = @p5 WHERE (([ProductID] = @p6) AND ([ProductName] = @p7) AND ((@p8 = 1 AND [CategoryID] IS NULL) OR ([CategoryID] = @p9)) AND ((@p10 = 1 AND [UnitPrice] IS NULL) OR ([UnitPrice] = @p11)) AND ((@p12 = 1 AND [UnitsInStock] IS NULL) OR ([UnitsInStock] = @p13)) AND ((@p14 = 1 AND [UnitsOnOrder] IS NULL) OR ([UnitsOnOrder] = @p15))) This is a lengthy statement indeed. The row to be updated is identified by its ID, but the oper- ation doesn’t take place if any of the other fields don’t match the value read into the DataSet. This statement will fail to update the corresponding row in the Products table if it has already been edited by another user. The last member of the ConflictOption enumeration, the CompareRowVersion option, works with tables that have a TimeStamp column, which is automatically set to the time of the update. If the row has a time stamp that’s later than the value read when the DataSet was populated, it means that the row has been updated already by another user and the UPDATE statement will fail. The SimpleDataSet sample project, which is discussed later in this chapter and demon- strates the basic DataSet operations, generates the UPDATE/INSERT/DELETE statements for the Categories and Products tables with the help of the CommandBuilder class and displays them on the form when the application starts. Open the project to examine the code, and change the setting of the ConflictOption property to see how it affects the autogenerated SQL statements. Accessing the DataSet’s Tables The DataSet consists of one or more tables, which are represented by the DataTable class. Each DataTable in the DataSet may correspond to a table in the database or a view. When you exe- cute a query that retrieves fields from multiple tables, all selected columns will end up in a single DataTable of the DataSet. You can select any DataTable in the DataSet by its index or its name: DS.Tables(0) DS.Tables("Customers") Each table contains columns, which you can access through the Columns collection. The Columns collection consists of DataColumn objects, with one DataColumn object for each column in the corresponding table. The Columns collection is the schema of the DataTable object, and the DataColumn class exposes properties that describe a column. ColumnName is the column’s name, DataType is the column’s type, MaxLength is the maximum size of text columns, and so on. The AutoIncrement property is True for Identity columns, and the AllowDBNull property determines whether the column allows Null values. In short, all the properties you can set visually as you design a table are also available to your code through the Columns collection of the DataTable object. You can use the DataColumn class’s properties to find out the structure of the table or to create a new table. To add a table to a DataSet, you can create a new DataTable object. Then create a DataColumn object for each column, set its 708 CHAPTER 16 DEVELOPING DATA-DRIVEN APPLICATIONS properties, and add the DataColumn objects to the DataTable Columns collection. Finally, add the DataTable to the DataSet. The process is described in detail in the online documentation, so I won’t repeat it here. Working with Rows As far as data are concerned, each DataTable consists of DataRow objects. All DataRow objects of a DataTable have the same structure and can be accessed through an index, which is the row’s order in the table. To access the rows of the Customers table, use an expression like the following: DS.Customers.Rows(iRow) where iRow is an integer value from zero (the first row in the table) up to DS.Customers.Rows .Count – 1 (the last row in the table). To access the individual fields of a DataRow object, use the Item property. This property returns the value of a column in the current row by either its index, DS.Customers.Rows(0).Item(0) or its name: DS.Customers.Rows(0).Item("CustomerID") To iterate through the rows of a DataSet, you can set up a For…Next loop like the following: Dim iRow As Integer For iRow = 0 To DSProducts1.Products.Rows.Count - 1 ‘ process row: DSProducts.Products.Rows(iRow) Next Alternatively, you can use a For Each…Next loop to iterate through the rows of the DataTable: Dim product As DataRow For Each product In DSProducts1.Products.Rows ‘ process prodRow row: ‘ product.Item("ProductName"), ‘ product.Item("UnitPrice"), and so on Next To edit a specific row, simply assign new values to its columns. To change the value of the ContactName column of a specific row in a DataTable that holds the customers of the North- wind database, use a statement like the following: DS.Customers(3).Item("ContactName") = "new contact name" STORING DATA IN DATASETS 709 The new values are usually entered by a user on the appropriate interface, and in your code you’ll most likely assign a control’s property to a row’s column with statements like the following: If txtName.Text.Trim <> "" Then DS.Customers(3).Item("ContactName") = txtName.Text Else DS.Customers(3).Item("ContactName") = DBNull.Value End If The code segment assumes that when the user doesn’t supply a value for a column, this col- umn is set to null (if the column is nullable, of course, and no default value has been specified). If the control contains a value, this value is assigned to the ContactName column of the fourth row in the Customers DataTable of the DS DataSet. Handling Null Values An important (and quite often tricky) issue in coding data-driven applications is the handling of Null values. Null values are special, in the sense that you can’t assign them to control prop- erties or use them in other expressions. Every expression that involves Null values will throw a runtime exception. The DataRow object provides the IsNull method, which returns True if the column specified by its argument is a Null value: If customerRow.IsNull("ContactName") Then ‘ handle Null value Else ‘ process value End If In a typed DataSet, DataRow objects provide a separate method to determine whether a spe- cific column has a Null value. If the customerRow DataRow belongs to a typed DataSet, you can use the IsContactNameNull method instead: If customerRow.IsContactNameNull Then ‘ handle Null value for the ContactName Else ‘ process value: customerRow.ContactName End If If you need to map Null columns to specific values, you can do so with the ISNULL() func- tion of T-SQL, as you retrieve the data from the database. In many applications, you want to display an empty string or a zero value in place of a Null field. You can avoid all the compar- isons in your code by retrieving the corresponding field with the ISNULL() function in your SQL statement. Where the column name would appear in the SELECT statement, use an expres- sion like the following: ISNULL(customerBalance, 0.00) 710 CHAPTER 16 DEVELOPING DATA-DRIVEN APPLICATIONS If the customerBalance column is Null for a specific row, SQL Server will return the numeric value zero. This value can be used in reports or other calculations in your code. Notice that the customer’s balance shouldn’t be Null. A customer always has a balance, even if it’s zero. When a product’s price is Null, it means that we don’t know the price of the product (and there- fore can’t sell it). In this case, a Null value can’t be substituted with a zero value. You must always carefully handle Null columns in your code, and how you’ll handle them depends on the nature of the data they represent. Adding and Deleting Rows To add a new row to a DataTable, you must first create a DataRow object, set its column val- ues, and then call the Add method of the Rows collection of the DataTable to which the new row belongs, passing the new row as an argument. If the DS DataSet contains the Customers DataTable, the following statements will add a new row for the Customers table: Dim newRow As New DataRow = dataTable.NewRow newRow.Item("CompanyName") = "new company name" newRow.Item("CustomerName") = "new customer name" newRow.Item("ContactName") = "new contact name" DS.Customers.Rows.Add(newRow) Notice that you need not set the CustomerID column. This column is defined as an Identity column and is assigned a new value automatically by the DataSet. Of course, when the row is submitted to the database, the ID assigned to the new customer by the DataSet may already be taken. SQL Server will assign a new unique value to this column when it inserts it into the table. It’s recommended that you set the AutoIncrementSeed property of an Identity column to 0 and the AutoIncrement property to –1 so that new rows are assigned consecutive negative IDs in the DataSet. Presumably, the corresponding columns in the database have a positive Identity setting, so when these rows are submitted to the database, they’re assigned the next Identity value automatically. If you’re designing a new database, use globally unique identifiers (GUIDs) instead of Identity values. A GUID can be created at the client and is unique: It can be generated at the client and will also be inserted in the table when the row is committed. To create GUIDs, call the NewGuid method of the Guid class: newRow.Item("CustomerID") = Guid.NewGuid To delete a row, you can remove it from the Rows collection with the Remove or RemoveAt method of the Rows collection, or you can call the Delete method of the DataRow object that represents the row. The Remove method accepts a DataRow object as an argument and removes it from the collection: Dim customerRow As DS.CustomerRow customerRow = DS.Customers.Rows(2) DS.Customers.Remove(customerRow) The RemoveAt method accepts as an argument the index of the row you want to delete in the Rows collection. Finally, the Delete method is a method of the DataRow class, and you must apply it to a DataRow object that represents the row to be deleted: customerRow.Delete STORING DATA IN DATASETS 711 Deleting versus Removing Rows The Remove method removes a row from the DataSet as if it were never read when the DataSet was filled. Deleted rows are not always removed from the DataSet, because the DataSet maintains its state. If the row you’ve deleted exists in the underlying table (in other words, if it’s a row that was read into the DataSet when you filled it), the row will be marked as deleted but will not be removed from the DataSet. If it’s a row that was added to the DataSet after it was read from the database, the deleted row is actually removed from the Rows collection. You can physically remove deleted rows from the DataSet by calling the DataSet’s AcceptChanges method. However, after you’ve accepted the changes in the DataSet, you can no longer submit any updates to the database. If you call the DataSet RejectChanges method, the deleted rows will be restored in the DataSet. Navigating Through a DataSet The DataTables making up a DataSet may be related — they usually are. There are methods that allow you to navigate from table to table following the relations between their rows. For example, you can start with a row in the Customers DataTable, retrieve its child rows in the Orders DataTable (the orders placed by the selected customer), and then drill down to the details of each of the selected orders. The relations of a DataSet are DataRelation objects and are stored in the Relations property of the DataSet. Each relation is identified by a name, the two tables it relates to, and the fields of the tables on which the relation is based. It’s possible to create relations in your code, and the process is really quite simple. Let’s consider a DataSet that contains the Cate- gories and Products tables. To establish a relation between the two tables, create two instances of the DataTable object to reference the two tables: Dim tblCategories As DataTable = DS.Categories Dim tblProducts As DataTable = DS.Products Then create two DataColumn objects to reference the columns on which the relation is based. They’re the CategoryID columns of both tables: Dim colCatCategoryID As DataColumn = tblCategories.Columns("CategoryID") Dim colProdCategoryID As DataColumn = tblProducts.Columns("CategoryID") And finally, create a new DataRelation object, and add it to the DataSet: Dim DR As DataRelation DR = New DataRelation("Categories2Products", colCatCategoryID, colProdCategoryID) Notice that you need to specify only the columns involved in the relation, and not the tables to be related. The information about the tables is derived from the DataColumn objects. The first argument of the DataRelation constructor is the relation’s name. If the relation involves 712 CHAPTER 16 DEVELOPING DATA-DRIVEN APPLICATIONS multiple columns, the second and third arguments of the constructor become arrays of Data- Column objects. To navigate through related tables, the DataRow object provides the GetChildRows method, which returns the current row’s child rows as an array of DataRow objects, and the GetParentRow/GetParentRows methods, which return the current row’s parent row(s). GetParentRow returns a single DataRow object, and GetParentRows returns an array of DataRow objects. Because a DataTable may be related to multiple DataTables, you must also specify the name of the relation. Consider a DataSet with the Products, Categories, and Suppliers tables. Each row of the Products table can have two parent rows, depending on which relation you want to follow. To retrieve the product category, use a statement like the following: DS.Products(iRow).GetParentRow("CategoriesProducts") The product supplier is given by the following expression: DS.Products(iRow).GetParentRow("SuppliersProducts") If you start with a category, you can find out the related products with the GetChildRows method, which accepts as an argument the name of a Relation object: DS.Categories(iRow).GetChildRows("CategoriesProducts") To iterate through the products of a specific category (in other words, the rows of the Prod- ucts table that belong to a category), set up a loop like the following: Dim product As DataRow For Each product In DS.Categories(iRow). GetChildRows("CategoriesProducts") ’ process product Next Row States and Versions Each row in the DataSet has a State property. This property indicates the row’s state, and its value is a member of the DataRowState enumeration, whose members are described in Table 16.2. You can use the GetChanges method to find the rows that must be added to the under- lying table in the database, the rows to be updated, and the rows to be removed from the underlying table. If you want to update all rows of a DataTable, call an overloaded form of the DataAdapter Update method, which accepts as an argument a DataTable and submits its rows to the database. The edited rows are submitted through the UpdateCommand object of the appropriate DataAdapter, the new rows are submitted through the Insert- Command object, and the deleted rows are submitted through the DeleteCommand object. [...]... encoding="utf -8" ?> ... how to use LINQ to SQL As it happens, LINQ to SQL is not the only object-relational technology Microsoft has to offer In this chapter, you’ll discover Microsoft s latest data technology, the Entity Framework Released initially with Service Pack 1 of Microsoft NET Framework 3.5, it is the 2010 version of Visual Studio that ships with this data technology out of the box for the first time While LINQ to... CHAPTER 17 USING THE ENTITY DATA MODEL Creating a New Entity Data Model You can add an EDM to a majority of project types supported by Visual Studio 2010 For this exercise, you will start by creating a new Windows Forms project: 1 Open a new instance of Visual Studio 2010, and choose File New Project From the New Project dialog box, choose Windows Forms Application, rename the project to MyEFProject,... EDM Designer is a visual modeling tool that displays the model in the form of a entity-relationship diagram Using the EDM Designer The EDM Designer is displayed by default when you add a new ADO NET EDM to your project or click an EDM file (.edmx extension) in Visual Studio Figure 17.3 shows the EDM Designer with the Northwind EDM open Figure 17.3 The EDM Designer in Visual Studio 2010 The EDM diagram... connect to an existing database and let Visual Studio create entities based on the existing database structure Although this approach can work for existing projects, for a new project that is based on reverse engineering, it would result in a loss of important information in the conceptual model THE ENTITY FRAMEWORK: RAISING THE DATA ABSTRACTION BAR In Visual Studio 2010, you can start with a blank EDM... model-driven data solution The Entity Framework represents a central piece of Microsoft s long-term data access strategy With its emphasis on modeling and on the isolation of data and application layers, it promises to deliver a powerful data platform capable of supporting the applications through a complete application life cycle For a Visual Basic programmer, it brings working with the data under the familiar... representation of data where the problem domain is described in the form of entities and their relationships In Visual Studio, this model is called the Entity Data Model (EDM), and you will learn how to create the EDM in the next section Figure 17.1 shows the sample Entity Data Model diagram inside Visual Studio 2010 Entities generally can be identified by the primary key and have some important characteristics... the EDM through the EDM Designer Nevertheless, you should have a basic understanding of the artifacts that comprise the EDM and its structure The EDM native format is XML, and it can also be viewed and edited manually, as can any XML file To see the Visual Studio–generated EDM XML, first refresh the model and then open the EDM file in the Visual Studio XML Editor: 1 Refresh the EDM by regenerating the... method used to work with the ERM is an entity-relationship diagram Many tools have smart entity-relationship diagramming capabilities, including the ERwin Data Modeler, Microsoft Visio, Toad Data Modeler, and Visual Studio I will describe the Visual Studio entity-relationship capabilities in the ‘‘Creating a New Entity Data Model’’ section These tools are typically capable of transforming the conceptual... . name of the database table where the data originates. The four basic operations of the DataAdapter (which are none other than the four basic data-access operations of a client application) are also. code shown in Listing 16 .8 iterates through the rows of the Categories table that are in error and prints the description of the error in the Output window. Listing 16 .8: Retrieving and displaying. you must populate accordingly before executing a command. The DataAdapter class performs the two basic tasks of a data-driven application: It retrieves data from the database to populate a DataSet

Ngày đăng: 12/08/2014, 21:20

Từ khóa liên quan

Mục lục

  • Mastering Microsoft Visual Basic 2010

    • Part 5: Developing Data-Driven Applications

      • Chapter 16: Developing Data-Driven Applications

        • Performing Update Operations

        • VB 2010 at Work: The SimpleDataSet Project

        • The Bottom Line

        • Chapter 17: Using the Entity Data Model

          • The Entity Framework: Raising the Data Abstraction Bar

          • Putting the EDM to Work

          • Reverse-Engineering an Entity Data Model

          • The Bottom Line

          • Chapter 18: Building Data-Bound Applications

            • Working with Typed DataSets

            • Data Binding

            • Designing Data-Driven Interfaces the Easy Way

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

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

Tài liệu liên quan