WPF-EDM: 绑定存储过程

[原文作者]:Gavin Fu

[原文链接]:WPF-EDM: Binding to Stored Procedures

使用存储过程转换数据库数据

        在建立WPF 数据库应用程序时,你可能不希望直接显示从数据表中得到的原始数据。相反,你希望将原始数据转换成更有意义的信息来显示。你可以在你的WPF应用程序中转换数据,或者你可以在存储过程中实现转换的逻辑并将其绑定到你的应用程序中。使用存储过程转换数据提供了以下几点好处:

  • 模块化编程

        存储过程和数据表同时存储在数据库中。他们能被多个应用程序共享,而且可以被独立修改,这使得数据转换逻辑更容易维护。

        同时,存储过程可以简化应用程序的编写。存储过程可能会对多个数据表进行操作。 但编写应用程序的程序员们并不需要关心这些细节,他们只需关注输入和输出。

  • 更快地执行

        在存储过程第一次执行时,它会被解析和优化,并且编译好的存储过程会驻留在内存缓存中供以后使用,这样执行起来会快得多。

        存储过程结合数据绑定以及Visual Studio 2010丰富的工具支持,你可以轻松地建立应用程序来传递并显示数据库数据。我们会实现一个列出客户订购的所有产品总数的主从信息视图应用程序作为示例。

示例应用程序数据模型

        示例应用程序所使用的数据模型如下所示。在这里会用到四个数据表。每个客户可以有多个订单,每个订单会包含多个产品。

clip_image001

        在这个应用程序中,我们不想显示客户订单的所有详细信息。相反,我们只想显示客户所购买的产品,以及每样产品客户购买的总数。 为此,我们要创建存储过程,并将其存储在数据库中。 假设存储过程的名称是CustOrderHist

ALTER PROCEDURE CustOrderHist @CustomerID nchar(5)

AS

SELECT ProductName, Total=SUM(Quantity)

FROM Products P, [Order Details] OD, Orders O, Customers C

WHERE C.CustomerID = @CustomerID

AND C.CustomerID = O.CustomerID AND O.OrderID = OD.OrderID AND OD.ProductID = P.ProductID

GROUP BY ProductName

示例应用程序 存储过程绑定步骤

1. 打开Visual Studio 2010,创建新的C#或VB WPF应用程序,命名为“BindToSP”。

2. 添加新的EDM项 (命名为:Northwind.edmx)。 在向导中选择“从数据库生成”,选择“Customer”表和存储过程“CustOrderHist”,然后单击“完成”。

3. 在EDM设计器的“实体模型浏览器”窗口中,双击“CustOrderHist”打开“添加函数导入”对话框。

4. 在对话框中,点击“获取列信息”获取存储过程输出的列,然后点击“创建新的复杂类型”生成一个类(命名为CustOrderHist_Result)这个类相当于返回的列。

clip_image002

打开 Northwind.Designer.cs,你会看到一个新的函数加入到NorthwindEntities类中:

C#

public ObjectResult<CustOrderHist_Result> CustOrderHist(global::System.String customerID)

VB

public Function CustOrderHist(customerID As Global.System.String) As ObjectResult(Of CustOrderHist_Result) 

5. 打开 MainWindow.xaml,在主菜单栏使用"数据-> 显示数据源"打开数据源窗口,添加一个新的对象数据源,并在“数据源配置向导”对话框中选择在上面的步骤生成的复杂类型CustOrderHist_Result。

clip_image003

数据源窗口如下图所示。Customers和CustOrderHist_Result默认绑定到DataGrid控件上。

clip_image004

6. 从数据源窗口拖放Customers和CustOrderHist_Result到MainWindow.xaml设计器中。

两个CollectionViewSource实例将被添加到窗口的资源中,一个是Customer的,另一个是 CustOrderHist_Result的:

<Window.Resources>

        <CollectionViewSource x:Key="custOrderHist_ResultViewSource" d:DesignSource="{d:DesignInstance my:CustOrderHist_Result, CreateList=True}" />

        <CollectionViewSource x:Key="customersViewSource" d:DesignSource="{d:DesignInstance my:Customer, CreateList=True}" />

</Window.Resources>

两个DataGrid会被创建,一个绑定到"customerViewSource"和另一个绑定到“custOrderHist_ResultViewSource”。

此外,加载数据到CollectionViewSource实例的相关代码也会自动生成:

C#

private System.Data.Objects.ObjectQuery<Customer> GetCustomersQuery(NorthwindEntities northwindEntities)

{

// Auto generated code

System.Data.Objects.ObjectQuery<BindToSP1.Customer> customersQuery = northwindEntities.Customers;

// Returns an ObjectQuery.

return customersQuery;

}

private void Window_Loaded(obj
ect sender, RoutedEventArgs e)

{

System.Windows.Data.CollectionViewSource custOrderHist_ResultViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("custOrderHist_ResultViewSource")));

// Load data by setting the CollectionViewSource.Source property:

// custOrderHist_ResultViewSource.Source = [generic data source]

BindToSP1.NorthwindEntities northwindEntities = new BindToSP1.NorthwindEntities();

// Load data into Customers. You can modify this code as needed.

System.Windows.Data.CollectionViewSource customersViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("customersViewSource")));

System.Data.Objects.ObjectQuery<BindToSP1.Customer> customersQuery = this.GetCustomersQuery(northwindEntities);

customersViewSource.Source = customersQuery.Execute(System.Data.Objects.MergeOption.AppendOnly);

}

VB

Private Function GetCustomersQuery(ByVal NorthwindEntities As BindingToSP.NorthwindEntities) As System.Data.Objects.ObjectQuery(Of BindingToSP.Customer)

Dim CustomersQuery As System.Data.Objects.ObjectQuery(Of BindingToSP.Customer) = NorthwindEntities.Customers

‘Returns an ObjectQuery.

Return CustomersQuery

End Function

Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

Dim NorthwindEntities As BindingToSP.NorthwindEntities = New BindingToSP.NorthwindEntities()

‘Load data into Customers. You can modify this code as needed.

Dim CustomersViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CustomersViewSource"), System.Windows.Data.CollectionViewSource)

Dim CustomersQuery As System.Data.Objects.ObjectQuery(Of BindingToSP.Customer) = Me.GetCustomersQuery(NorthwindEntities)

CustomersViewSource.Source = CustomersQuery.Execute(System.Data.Objects.MergeOption.AppendOnly)

Dim CustOrderHist_ResultViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CustOrderHist_ResultViewSource"), System.Windows.Data.CollectionViewSource)

‘Load data by setting the CollectionViewSource.Source property:

‘CustOrderHist_ResultViewSource.Source = [generic data source]

End Sub

7. 在custormer DataGrid控件的SelectionChanged事件加入一些代码,修改及添加的代码如高亮处所示:

C#

private void Window_Loaded(object sender, RoutedEventArgs e)

{

this.northwindEntities = new BindToSP1.NorthwindEntities();

System.Windows.Data.CollectionViewSource custOrderHist_ResultViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("custOrderHist_ResultViewSource")));

// Load data by setting the CollectionViewSource.Source property:

// custOrderHist_ResultViewSource.Source = [generic data source]

BindToSP1.NorthwindEntities northwindEntities = new BindToSP1.NorthwindEntities();

// Load data into Customers. You can modify this code as needed.

System.Windows.Data.CollectionViewSource customersViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("customersViewSource")));

System.Data.Objects.ObjectQuery<BindToSP1.Customer> customersQuery = this.GetCustomersQuery(northwindEntities);

customersViewSource.Source = customersQuery.Execute(System.Data.Objects.MergeOption.AppendOnly);

// Load data by setting the CollectionViewSource.Source property:

custOrderHist_ResultViewSource.Source = this.orderCollection;

}

private ObservableCollection<CustOrderHist_Result> orderCollection = new ObservableCollection<CustOrderHist_Result>();

private NorthwindEntities northwindEntities;

// This is the added handler of customer DataGrid’s SelectionChanged event

private void customersDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)

{

Customer selectedCustomer = this.customersDataGrid.SelectedItem as Customer;

if (selectedCustomer != null)

{

orderCollection.Clear();

foreach (CustOrderHist_Result result in this.northwindEntities.CustOrderHist(selectedCustomer.CustomerID))

{

orderCollection.Add(result);

}

}

}

VB

Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

Me.NorthwindEntities = New BindingToSP.NorthwindEntities()

‘Load data into Customers. You can modify this code as needed.

Dim CustomersViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CustomersViewSource"), System.Windows.Data.CollectionViewSource)

Dim CustomersQuery As System.Data.Objects.ObjectQuery(Of BindingToSP.Customer) = Me.GetCustomersQuery(NorthwindEntities)

CustomersViewSource.Source = CustomersQuery.Execute(System.Data.Objects.MergeOption.AppendOnly)

Dim CustOrderHist_ResultViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CustOrderHist_ResultViewSource"), System.Windows.Data.CollectionViewSource)

‘Load data by setting the CollectionViewSource.Source property:

CustOrderHist_ResultViewSource.Source = Me.orderCollection

End Sub

Private orderCollection As ObservableCollection(Of CustOrderHist_Result) = New ObservableCollection(Of CustOrderHist_Result)

Private NorthwindEntities As NorthwindEntities

‘This is the added handler of customer DataGrid’s SelectionChanged event

Private Sub CustomersDataGrid_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs)

Dim selectedCustomer As Customer = CType(Me.CustomersDataGrid.SelectedItem, Customer)

If selectedCustomer IsNot Nothing Then

orderCollection.Clear()

For Each result As CustOrderHist_Result In Me.NorthwindEntities.CustOrderHist(selectedCustomer.CustomerID)

orderCollection.Add(result)

Next

End If

End Sub

8. 运行该应用程序,选择custormer数据表中的任意客户,custOrderHist数据表会刷新并显示选中客户所定购产品的详细信息:

clip_image005

结论

       你可能注意到尽管四个表都包含在之前的步骤中,但只有Customer表被导入到EDM中。存储过程可以由专门的数据库程序员在数据库上创建,这样应用程序的程序员甚至不会注意到有另外的三个表!这大大减少了应用程序的复杂性,并使它更容易维护。

        就是这些了。享受它吧!

如何绑定数据集到WPF设计器

[原文作者]:Yao Hai

[原文链接]:WPF Data Binding: How to Bind DataSet to WPF Designer

        在我们前面的章节中,对于WPF数据绑定方案,我们常常使用的是实体数据模型作为ORM(对象关系映射)层。

        同时,自从数据集的广泛使用, 我们也提供对数据集作为数据源在数据绑定方法。下面的例子是使用C#一步一步实现的,这个例子是基于Visual Studio 2010 Beta2 版本。

绑定数据集到WPF 设计器

        1. 打开Visual Studio 从主菜单: 文件->新建项目,选择WPF 应用程序 创建一个新的C#应用程序。这里我们就使用项   目默认的名称: WpfApplication1。

        2. 从主菜单: 数据-> 增加新的数据源…->数据库->数据集触发数据源向导。

        3. 根据这个向导连接到数据库Northwind,然后选择表Customers,Orders。完成这一步以后,下面的截图就是数据库显示的状态。

image 

        4. 从主菜单: 数据->显示数据源显示数据源工具窗口,确保MainWindow.xaml是个活动的窗口。

        5. 切换到数据源工具窗口,你可以单击”Customers” 节点和下拉菜单。在context菜单中, 选择”自定义…”, 一个”自定义控制绑定”的对话框弹出, 在组合框中选择”[List]”作为数据类型。

image

        6. 绑定”Customers”表到组合框控件,经过上面的步骤后,下面的是拖放前的快照。从这个图标中,你可以看到”Customers” 已经绑定到组合框。

image

        7. 把”Customers”表拖到左上角,然后把”Customers.Orders”拖到WPF设计器的中间。布局如下面所示:

clip_image004[4]

        这些步骤以后, 一个简单的数据集 Master-Detail应用程序已经创建。你可以按下”Ctrl + F5”运行这个程序去查看这个数据。

说明:

        在第五步中,有2部分代码生成: 一部分是xaml标记代码和C#的后台代码。这些代码是用着示范。我解释一下它们之间的对应关系以至于提供一个指导方案来修改这些代码。

        这些xaml代码,除了标记控制/数据绑定外,它们都相同的使用了EDM作为PRM层。它也生成资源块。

<Window.Resources>

        <my:NorthwindDataSet x:Key="NorthwindDataSet" />

        <CollectionViewSource x:Key="customersViewSource" Source="{Binding Path=Customers, Source={StaticResource NorthwindDataSet}}" />

        <CollectionViewSource x:Key="customersOrdersViewSource" Source="{Binding Path=FK_Orders_Customers, Source={StaticResource customersViewSource}}" />

</Window.Resources>

        第一行定义了一个NorthwindDataSet的实例,它具体到数据集。所以当一个窗口初始化时,就有一个数据集实例创建。第二和第三行代码是利用CollectionViewSource做master-details data-binding。

下面的是C#代码:

private void Window_Loaded(object sender, RoutedEventArgs e)

{

WpfApplication1.NorthwindDataSet NorthwindDataSet = ((WpfApplication1.NorthwindDataSet)(this.FindResource("NorthwindDataSet")));

// Load data into the table Customers. You can modify this code as needed.

WpfApplication1.NorthwindDataSetTableAdapters.CustomersTableAdapter northwindDataSetCustomersTableAdapter = new WpfApplication1.NorthwindDataSetTableAdapters.CustomersTableAdapter();

northwindDataSetCustomersTableAdapter.Fill(NorthwindDataSet.Customers);

System.Windows.Data.CollectionViewSource customersViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("customersViewSource")));

customersViewSource.View.MoveCurrentToFirst();

// Load data into the table Orders. You can modify this code as needed.

WpfApplication1.NorthwindDataSetTableAdapters.OrdersTableAdapter northwindDataSetOrdersTableAdapter = new WpfApplication1.NorthwindDataSetTableAdapters.OrdersTableAdapter();

northwindDataSetOrdersTableAdapter.Fill(NorthwindDataSet.Orders);

System.Windows.Data.CollectionViewSource customersOrdersViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("customersOrdersViewSource")));

customersOrdersViewSource.View.MoveCurrentToFirst();

}

        第一行,从WPF设计资源字典得到一个数据集实例。第二行是导入Customers数据到表中。当然你也可以用你喜欢的数据集实例替换它。第三和第四行是初始化CollectionViewSource。第5到7行,对于Customers.Orders表,和第二、第四行相似。

        你可能注意到当应用程序开始运行时数据开始导入。如果你想根据需要导入,你可以把这些代码放到事件中比如按钮的单击事件或者其他的一些事件。

Visual Studio 2010 中WPF拖拽式数据绑定

[原文作者]:Milind Lele
 
08年11月19日12:39PM 上传
VS的2010版本(点击此处下载)比以前的一个改进的地方是WPF的拖拽式数据绑定,WinForms的拖拽式数据绑定在Visual Studio 2005就已经存在了。我们在VS2010把拖拽式数据绑定应用到了WPF上。如果你熟悉WinForms的数据绑定,你会发现WPF的拖拽式数据绑定和WinForms的很相似。
 
VS2008 SP1让我们可以在工程中添加一个EDM(实体数据模型),在VS2010中,添加了在数据源窗口中支持EDM这一功能,所以如果添加了一个EDM到工程中,这个EDM可以显示在数据源窗口中,你知道拖动这个EDM到WPF窗体中就可以完成绑定了。
具体信息我发布在Visual Studio Data blog上了。
你可以下载CTP体验一下,然后告诉我你的想法。

WPF在SP1中更好的数据编辑功能

[原文作者]:Beth Massi    
[原文链接]:Better Data Editing Features in WPF with SP1       
 
 
   当我开始钻研数据绑定时,在WPF中我最想念的一项功能,是在BindingListCollectionView和我所喜欢的winforms BindingSource之间的一致性功能。BindingListCollectionView就像在winforms下的资源捆绑一样,提供了限制收集数据(或数据表)的导航、流动、过滤和排序。
   然而在合集中并不支持处理添加和删除项目。你可以从我的WPF窗体数据录像中注意到,当我从数据中添加或者删除一行时,我只能直接进入数据表进行操作。当我用数据表工作时这不会有问题,因为他们可以做他们自己的处理编辑(连同修改记录)。不过,为了有良好的客户业务集合,实现典型的绑定接口,这通常是一个必要的功能
SP1在WPF’s BindingListCollectionView中增加了新的属性和方法
   发布的Visual Studio/.NET FX SP1已经增强了BindingListCollectionView,包括新的属性和方法:CanAddNew 属性, CanCancelEdit 属性, CanRemove属性, CurrentAddItem属性, CurrentEditItem属性, IsAddingNew属性, IsEditingItem属性, ItemProperties属性, NewItemPlaceholderPosition属性, AddNew方法, CancelEdit 方法, CancelNew 方法, CommitEdit 方法, CommitlNew 方法, EditItem 方法, Remove 方法, RemoveAt 方法.
   有一件事情我想在这里指出,不同的是,在Winforms中,无论是修改或增加数据源(也就是数据表或集合),我们都习惯于访问BindingSource上的EndEdit来实现处理变换。在WPF中,有一个CommitEditCommitNew的单独访问,如果在增加的时候调用了AddNew,你必须要确认你不是在调用CommitEdit,否则你就只能调用CommitNew。我不知道他们为什么把这两者区分开来。在使用DataSets时,我总是几乎立刻提交处理(如在填写默认值后),并且可以通过Accept/RejectChanges方法来使用DataSet的修改记录。
 
用新的AddNew和Remove Methods来增加或删除数据
   当我们想在数据表中增加一行新的数据,我们现在可以直接调用BindingListCollectionView中的AddNew。例如:
Private OrderData As New OrdersDataSet
Private OrdersViewSource As BindingListCollectionView
Sub New()
    ‘ This call is required by the Windows Form Designer.
    InitializeComponent()
 
    ‘ Add any initialization after the InitializeComponent() call.
    Me.LoadData()
    Me.DataContext = Me.OrderData.Orders
    Me.OrdersViewSource = CollectionViewSource.GetDefaultView(Me.DataContext)
End Sub
Private Sub AddNewOrder()
    ‘— Old Code —
    ‘Add a new row to the collection
    ‘Dim order = Me.OrderData.Orders.NewOrdersRow
    ‘Me.OrderData.Orders.AddOrdersRow(order)
    ‘Up to us to update the position
    ‘Me.OrdersViewSource.MoveCurrentToLast()
 
    ‘— New Code —
    ‘Add a new row to the collection
    Me.OrdersViewSource.AddNew()
    ‘Push changes into the DataTable
    Me.OrdersViewSource.CommitNew()
End Sub
 
 
   首先我通过DataSetpartial类来处理所有的验证和设置的默认值,因此如果我们工作于自己的商业对象集合时,AddNewOrder()方法仍将保持不变。这就是为什么我习惯于用Winforms,因为你可以轻松地交换数据源,又不会弄乱数据绑定代码。不过需要指出的是,如果CommitNewDataRow无法被调用,你再写入DataSet时就会报错。所以一定要确保调用在数据表partial类中的TableNewRow事件,并且写入有效的默认值。
删除当前行也简单:
Private Sub RemoveOrder()
    If Me.OrdersViewSource.CurrentPosition > -1 Then
        ‘— Old Code —
        ‘Dim order As OrdersDataSet.OrdersRow
        ‘order = CType(CType(Me.OrdersViewSource.CurrentItem, DataRowView).Row, OrdersDataSet.OrdersRow)
        ‘order.Delete()
 
        ‘— New Code —
        Me.OrdersViewSource.Remove(Me.OrdersViewSource.CurrentItem)
    End If
End Sub 
 
WPF中使用Master-Detail窗体
 
   这些都是很好的改进,但是由于BindingSource是一个可视化的组件,并且能够处理CurrencyManager 目前Winforms中的BindingSource仍然比IMHO方便。当你在WPF中用master-detail绑定是一件痛苦的事情。你必须获得一个BindingListCollectionView的详细引用,每次detail view有变动,都需要再次引用(这将是ListBox或者ListView上的一个ItemSource)。如果你在XAML中能正确地建立绑定,那么当父模块变换时detail view就会自动变换。然而,如果你想在子模块中用AddNew,你就需要实时在每次都获得一个代码的引用,因为视图是动态的。Winforms 绑定源处理这种情况更好些。
   尽管我们需要每次都获得引用,这也是相当简单的:
Private Sub AddNewDetail()
    If Me.OrdersViewSource.CurrentPosition > -1 Then
        ‘— Old Code —
        ‘Dim order As OrdersDataSet.OrdersRow
        ‘order = CType(CType(Me.OrdersViewSource.CurrentItem, DataRowView).Row, _
        ‘              OrdersDataSet.OrdersRow)
        ‘Dim detail = Me.OrderData.OrderDetail.NewOrderDetailRow
        ‘detail.OrderID = order.OrderID
        ‘Me.OrderData.OrderDetail.AddOrderDetailRow(detail)
 
        ‘— New Code —
        Dim detailView As BindingListCollectionView = _
                          CollectionViewSource.GetDefaultView(Me.lstDetails.ItemsSource)
        detailView.AddNew()
        ‘Note that the related OrderID is set for us automatically just like Winforms
        detailView.CommitNew()
    End If
End Sub
 
Private Sub RemoveDetail()
    If Me.OrdersViewSource.CurrentPosition > -1 Then
 
        Dim detailView As BindingListCollectionView = _
                          CollectionViewSource.GetDefaultView(Me.lstDetails.ItemsSource)
 
        If detailView.CurrentPosition > -1 Then
            ‘— Old Code —
            ‘Dim detail As OrdersDataSet.OrderDetailRow
            ‘detail = CType(CType(Me.OrderDetailsViewSource.CurrentItem, DataRowView).Row, _
            ‘               OrdersDataSet.OrderDetailRow)
            ‘detail.Delete()
 
            ‘— New Code —
            detailView.Remove(detailView.CurrentItem)
        End If
    End If
End Sub
 
   通过使用新的AddNewRemove方法似乎也能解决一些问题,我曾经遇到过使用LINQ to SQL产生子模块集合(EntitySets),所以最好开始运用这些新方法的优势。在以后的博客帖子中,我会用我们所做的LINQ to SQL N-Tier application,在末尾补上WPF Front
   我可能不会更新我们已经做的工作的视频,但以后我会在适当的时候使用这些新的方法和属性,所以请下载SP1
Enjoy!

基于WPF LINQ的动态数据模型

[原文作者]:Beth Massi

[原文链接]:Dynamic Data Entry with WPF and LINQ

   上一节我讲到用XML编写动态WPF UI, 尽管这里的UI是动态生成的,但仍有一处不尽人意的地方,就是我们采用的是一个具体的对象customer(来源于LINQ to SQL classes)。 如果想要我们的应用程序既能够动态生成WPF UI 又能动态地编辑处理数据库里的任何表数据,就需要进一步参数化程序代码 ——而不仅限于customer。 这样只需要修改数据库表的定义而不用更新对象模块和重新编译代码。

   实现方法是试图在运行时加载,处理一个简单的无类型的(或者称作通用类型)DataTable,并运用XML literals特性 –为SqlDataAdapter生成SELECT和UPDATE语句。要注意一件事情,这个过程必须依赖数据库验证规则。因此这种方式只能用于处理非常简单的表(缩略语.维护表)。

 
   我需要创建一个强类型(指定类型)的DataTable包含表的定义,用来取代LINQ to SQL模板。这个datatable存储了字段的元数据(字段名,类型,等等)以便我们对表的操作。右键点击项目工程,添加新模板,选择DataSet template。我将它命名为TableSchemaDataSet,然后拖拽Server Explorer-〉Data Connection –〉Northwind Database下的存储过程GetTableSchema(这个已经在前面章节中生成好了)到DataSet 设计器上,自动为我们生成所需的DataTable,重命名为TableSchema,并保存。

 
   接下来精彩的部分。因为我们并不想知道当前表的定义,我们会加载一个通用类型的DataTable生成一个动态的WPF 窗口。DataTable和DataSet都建好了,还有两个步骤需要手动设置。

   首先定义一个公共属性(Public Property)存放表名,同时设置默认值为“Shippers”,然后创建一批私有变量引用所需的ADO.NET对象。

Imports <xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

Imports <xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

Imports System.Windows.Markup

Imports System.Data.SqlClient

Imports System.Data

 

Partial Public Class Window2

    ‘This is the metadata table we created in the DataSet Designer

    Private TableSchema As New TableSchemaDataSet.TableSchemaDataTable

    ‘ADO.NET objects used to load and save the table we’re editing

    Private TableDataAdapter As New SqlDataAdapter

    Private TableConnection As New SqlConnection(My.Settings.NorthwindConnectionString)

    Private Table As DataTable

    ‘This is the key field used in searching for a row in this example

    Private PKField As TableSchemaDataSet.TableSchemaRow

 

    ‘This property can be set before the Form.Show() to edit any table

    Private m_tableName As String = "Shippers"

    Public Property TableName() As String

        Get

            Return m_tableName

        End Get

        Set(ByVal value As String)

            m_tableName = value

        End Set

    End Property

 


    现在UI定义好了,然后设置主键字段(TableSchemaDataRow 对象)以便我们点击Find按钮时运用到Update和Select操作。

Private Sub Window1_Loaded() Handles MyBase.Loaded

    Try

        ‘Get the schema of the database table we want to edit

        Dim taSchema As New TableSchemaDataSetTableAdapters.TableSchemaTableAdapter

        taSchema.Fill(Me.TableSchema, Me.TableName)

 

        ‘Create the DataTable that will hold the record we’re editing

        Me.Table = New DataTable(Me.TableName)

        Me.Title = Me.TableName

        Me.LoadUI()

        Me.SetPrimaryKey()

        Me.SetUpdateCommand()        

    Catch ex As Exception
        MsgBox(ex.ToString)
        Me.Close()
    End Try
End Sub
 
Private Sub LoadUI()
 
    Dim UI = <Grid xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 Name="Grid1">
                 <Grid.ColumnDefinitions>
                     <ColumnDefinition Width="100*"/>
                     <ColumnDefinition Width="200*"/>
                 </Grid.ColumnDefinitions>
                 <StackPanel Name="StackLabels" Margin="3">
                     <%= From column In Me.TableSchema _
                         Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _
                         Select <Label
                                    Height="28"
                                    Name=<%= column.ColumnName & "Label" %>
                                    HorizontalContentAlignment="Right">
                                    <%= column.ColumnName %>:</Label> %>
                 </StackPanel>
                 <StackPanel Grid.Column="1" Name="StackFields" Margin="3">
                     <%= From column In Me.TableSchema _
                         Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _
                         Select GetUIElement(column) %>
                 </StackPanel>
             </Grid>

     Me.DynamicContent.Content = XamlReader.Load(UI.CreateReader())
 
End Sub
 
Private Function GetUIElement(ByVal columnInfo As TableSchemaDataSet.TableSchemaRow) As XElement
    Select Case columnInfo.DataType.ToLower
        Case "datetime", "int", "smallint", "money"
            Return <TextBox
                       Height="28"
                       Name=<%= "txt" & columnInfo.ColumnName %>
                       Text=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>/>
        Case "bit"
            Return <CheckBox
                       HorizontalContentAlignment="Left"
                       Name=<%= "chk" & columnInfo.ColumnName %>
                       IsChecked=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>>
                       <%= columnInfo.ColumnName %>
                   </CheckBox>
        Case Else
            Return <TextBox
                       Height="28"
                       Name=<%= "txt" & columnInfo.ColumnName %>
                       MaxLength=<%= columnInfo.MaxLength %>
                       Text=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>/>
    End Select
End Function

 

Private Sub SetPrimaryKey()    这样我们就可以通过设置TableName,动态生成UI,通过Find找到想要的数据,并可以修改保存数据。而且可以不用重新编译修改数据库表结构。
 

    ‘Grab the Primary Key column of the table we want to edit so we can use it in the search

    Me.PKField = (From column In Me.TableSchema Where column.IsPrimaryKey = 1).FirstOrDefault()

End Sub

Private Sub btnFind_Click() Handles btnFind.Click

    If Me.txtSearch.Text <> "" Then

        Try

            ‘Create the SELECT command

            Dim cmdText = <s>

                          SELECT * FROM <%= Me.TableName %>

                          WHERE <%= Me.PKField.ColumnName %> =

                                <%= If(Me.PKField.DataType.Contains("char"), _

                                    "’" & Me.txtSearch.Text & "’", _

                                    Me.txtSearch.Text) %>

                          </s>.Value

 

            Dim cmd As New SqlCommand(cmdText, Me.TableConnection)

            Me.Table.Clear()

            Me.TableDataAdapter.SelectCommand = cmd

            Me.TableDataAdapter.Fill(Me.Table)

 

            Me.DataContext = Me.Table

            Dim view = CollectionViewSource.GetDefaultView(Me.Table)

            view.MoveCurrentToFirst()

 

        Catch ex As Exception

            MsgBox(ex.ToString)

            Me.DataContext = Nothing

        End Try

    Else

        Me.DataContext = Nothing

    End If

End Sub

 

Private Sub SetUpdateCommand()

    ‘Set the UpdateCommand so that we can save edited records in the table

    Dim cmdText = <s>

                  UPDATE <%= Me.TableName %>

                  SET <%= From column In Me.TableSchema _

                          Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _

                          Select <c>

                                     <%= column.ColumnName %> = @<%= column.ColumnName %>

                                     <%= If(Me.TableSchema.Rows.IndexOf(column) < _

                                            Me.TableSchema.Rows.Count – 1, ", ", "") %>

                                 </c>.Value %>

                  WHERE <%= Me.PKField.ColumnName %> = @<%= Me.PKField.ColumnName %>

                        <%= From column In Me.TableSchema _

                            Where column.IsPrimaryKey = 0 AndAlso column.DataType = "timestamp" _

                            Select <c>

                                     AND <%= column.ColumnName %> = @<%= column.ColumnName %>

                                   </c>.Value %>

                  </s>.Value

 

    Dim cmd As New SqlCommand(cmdText, Me.TableConnection)

    Dim p As SqlParameter

 

    For Each column In Me.TableSchema

        If column.IsPrimaryKey = 0 AndAlso column.DataType = "timestamp" Then

            ‘Note: It’s recommended to use a TimeStamp column in your tables for concurrency checking

            p = New SqlParameter("@" & column.ColumnName, SqlDbType.Timestamp)

            p.SourceVersion = DataRowVersion.Original

            p.SourceColumn = column.ColumnName

            cmd.Parameters.Add(p)

        Else

            p = New SqlParameter("@" & column.ColumnName, _

                                 CType([Enum].Parse(GetType(SqlDbType), column.DataType, True), SqlDbType))

            p.SourceColumn = column.ColumnName

            p.SourceVersion = DataRowVersion.Current

            cmd.Parameters.Add(p)

        End If

    Next

 

    Me.TableDataAdapter.UpdateCommand = cmd

End Sub

 

    然后我们可以在Loaded事件处理函数中编写逻辑代码,加载元数据,创建和加载XAML显示UI,再然后就是处理TableDataAdaper上的UpdateCommand。