65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 2 数据库更新

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.08/5 (7投票s)

2008年8月31日

CPOL

8分钟阅读

viewsIcon

152578

downloadIcon

1237

从 Silverlight 应用程序执行数据库更新。

引言

本文演示了从 Silverlight 2 应用程序更新数据库的方法。

随着最近 Silverlight 2 Beta 2 的发布,访问和操作数据库数据变得更加容易。由于 Silverlight 应用程序无法直接访问本地资源,因此数据访问是通过 Web 服务(如 Windows Communications Foundation (WCF) 或 ASMX)提供的。有许多示例演示了如何读取数据并填充数据对象,但很少有示例提供将更改写回数据源的任何功能。

在这里找到的一个示例 此处 使用了 ADO.NET Data Services (Astoria),该服务仍在开发中。

另一个解决方案,在 此处 找到,使用了 LINQ,并复制了原始数据。更新时,它会将原始数据和修改后的数据集都发送回服务器。因此,如果您有 50 行数据,您最多会发送 100 行数据。虽然这种方法有效,但我不喜欢即使只想做一些小的更改也要发送大量数据的想法。该页面包含一个视频,其源代码绝对值得一看。

本文演示了一种仅发送更改回数据库的方法。它假定您具备一些基本的 Silverlight 2 操作知识,例如调用 Web 服务和将数据绑定到控件。文章《我的第一个 Silverlight 数据项目》为此类操作提供了良好的介绍。另一个资源是 Silverlight 入门页面。

源代码使用 Visual Studio 2008 和 Silverlight 2 Beta 2 中的 C# 编写。

Using the Code

随附的源代码解决方案包含五个项目

DataWebService 通过 WCF 使用 LINQ 公开 Northwind 表。此服务还包含将 Silverlight 客户端传递的更改更新到数据库的逻辑。
UpdateChangesToLINQ 使用 LINQ 数据上下文提供更新更改的逻辑。它由 _DataWebServices_ 项目使用。
TrackChanges 跟踪对象更改。用于 _SilverlightUpdateChanges_。
DemoUpdateClass 演示数据更新的控制台应用程序。有助于调试 _UpdateChanges_ 类。
SilverlightUpdateChanges 一个基本的 Silverlight 应用程序,显示绑定到 Northwind _Customer_ 表的 _DataGrid_。通过调用 _DataWebService_ 来填充此表。

在运行示例之前,请在 _DataWebService_ 中更改路径,使其指向包含的 Northwind 数据库 MDF 文件。这是标准的 Northwind 示例数据库,并附加了一个 _testtable_ 表。此表包含大多数 SQL 数据类型,可用于测试各种类型,并且还可以显示 Silverlight 网格如何处理它们。

该解决方案有两个主要部分:跟踪 Silverlight 应用程序中进行的更改,以及提交更改以更新数据库。

跟踪更改

当将 Web 服务引用添加到 Silverlight 项目时,VS 2008 将生成代理类。这些类基于服务公开的类。因此,如果服务包含使用 _Customer_ 对象的对象,则会在 Silverlight 端创建相应的 Customer 代理对象。这些代理类是隐藏的,但您可以通过在“解决方案资源管理器”中选择“显示所有文件”按钮来查看它们。它们存储在 _Reference.cs_ 文件中,每个服务一个。

我在代理类中注意到的是 _INotifyPropertyChanged_ 接口中实现的数据结构。这要求实现 _PropertyChanged_ 事件处理程序,该处理程序在进行属性更改时被调用。默认情况下,此事件处理程序未分配。

以下代码包含一个实现 _INotifyPropertyChanged_ 事件的简单 _Widget_ 类

public partial class Widget: object, System.ComponentModel.INotifyPropertyChanged
{
    private string _ID;
    private string _Name;
    private string _Colour;

    public string ID
    {
        get
        {
            return this._ID;
        }
        set
        {
            if ((object.ReferenceEquals(this._ID, value) != true))
            {
                this._ID = value;
                this.RaisePropertyChanged("ID");
            }
        }
    }
    
    public string Name
    {
        get
        {
            return this._Name;
        }
        set
        {
            if ((object.ReferenceEquals(this._Name, value) != true))
            {
                this._Name= value;
                this.RaisePropertyChanged("Name");
            }
        }
    }

    public string Colour
    {
        get
        {
            return this._Colour;
        }
        set
        {
            if ((object.ReferenceEquals(this._Colour, value) != true))
            {
                this._Colour= value;
                this.RaisePropertyChanged("Colour");
            }
        }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
        System.ComponentModel.PropertyChangedEventHandler 
                              propertyChanged = this.PropertyChanged;
        if ((propertyChanged != null))
        propertyChanged(this, new 
                System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}

使用此事件处理程序的优点是,无论是通过代码还是数据控件进行更改,只要更改属性就会触发它。要连接事件处理程序,请将事件处理程序方法分配给对象。以下代码演示了如何将事件处理程序分配给 _Widget_ 对象。每次 _Widget_ 属性发生更改时,都会调用事件处理程序

public void TestHandler()
{
    //
    Widget testWidget = new Widget();
    testWidget.ID = "100";
    testWidget.PropertyChanged += new 
      System.ComponentModel.PropertyChangedEventHandler(this.Notifychanges);
    testWidget.Name = "Big Widget";
    testWidget.Colour = "Green";
}
public void Notifychanges(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    PropertyInfo p;
    p = sender.GetType().GetProperty(e.PropertyName);
    //get change property value
    string changeValue = p.GetValue(sender, null).ToString();
    
    System.Windows.Browser.HtmlPage.Window.Alert("Changing " + 
                   e.PropertyName + " to " + changeValue);
}

随附的 _TrackChanges_ 类库通过使用此事件处理程序来跟踪对象更改。任何对象属性更改都会导致事件触发。更改存储在 _Dictionary_ 对象中。

要在项目中使用此功能,请添加对 _TrackChanges_ 对象的引用。 _TrackChanges_ 公开了 _ChangeTracking_ 类。类构造函数需要三个参数:表名、键数组以及对象集合或单个对象实例。

表名应与 Web 服务 LINQ 数据源中的 _TableName_ 属性匹配(有关更多信息,请参见下文)。键数组是区分大小写的,它们必须与对象属性名称的大小写匹配。

在以下示例中,将创建单个 _Customer_ 实例并将其分配给 _ChangeTracking_ 对象

Customer testCustomer = new Customer();
//need to assign an ID for tracking to know what to track..
testCustomer.CustomerID = "10000";
//start tracking changes
ChangeTracking customerTrack = new ChangeTracking("dbo.Customers", 
               new string[] { "CustomerID" }, testCustomer);

testCustomer.CompanyName = "Test Company";
testCustomer.ContactName = "Fred Smith";
testCustomer.ContactTitle = "Consultant";

_SilverlightUpdateChanges_ 示例项目将 Northwind _Customers_ 表和 _testtable_ 的所有记录加载到数据网格中。所做的任何更改将在单击“保存更改”按钮时提交到数据库。

以下代码列出了 _SilverlightUpdateChanges_ 项目的一部分。加载表单后,通过调用 Web 服务方法来填充客户和 testable 网格。这将返回这些表的​​所有记录列表。创建 _ChangeTrack_ 类的实例,传入表名、键数组和要跟踪更改的对象。创建此类会在每个对象上将 _NotifyChanges_ 事件处理程序连接到类内的事件处理程序。

TrackChanges.ChangeTracking customerTracking;
TrackChanges.ChangeTracking testTracking;

void Page_Loaded(object sender, RoutedEventArgs e)
{
    //create an instance of the web service and get customers
    NorthwindSvc.NorthwindSvcClient nwindClient = 
                       new NorthwindSvc.NorthwindSvcClient();

    //add event handler for GetCustomers asynchronous call 
    //and call GetCustomersAsync to populate items
    nwindClient.GetCustomersCompleted += new 
      EventHandler<SilverlightUpdateChanges.NorthwindSvc.
      GetCustomersCompletedEventArgs>(client_GetAllCustomersCompleted);
    nwindClient.GetCustomersAsync();

    nwindClient.GetTestItemsCompleted += new 
      EventHandler<GetTestItemsCompletedEventArgs>(
      nwindClient_GetTestItemsCompleted);
    nwindClient.GetTestItemsAsync();
}

void nwindClient_GetTestItemsCompleted(object sender, 
                 GetTestItemsCompletedEventArgs e)
{
  //get list of test items
  List<testtable> testList = e.Result.ToList();

  //start tracking any changes to testList
  testTracking = new ChangeTracking("dbo.testtable", 
                 new string[] { "id" }, testList);

  grdTestItems.ItemsSource = testList;
  grdTestItems.Columns[0].IsReadOnly = true;
}


void client_GetAllCustomersCompleted(object sender, 
     SilverlightUpdateChanges.NorthwindSvc.GetCustomersCompletedEventArgs e)
{
  //get list of customers
  List<Customer> customerList = e.Result.ToList();

  //start tracking any changes to customerList
  customerTracking = new ChangeTracking("dbo.Customers", 
                     new string[] { "CustomerID" }, customerList);
  grdCustomers.ItemsSource = customerList;
}

所有更改都存储在 _ChangeTracking_ 类中的 _Dictionary_ 对象中。字典键包含记录的键以及被更改的字段/属性。这些值由分隔符分隔。默认分隔符是竖线 ('|')。因此,如果 Northwind _Customers_ 表的 _CustomerName_ 属性对公司记录“Around the Horn”进行了更改,则键将如下所示:_AROUT|CustomerName_。已更改的值存储在相应的字典 _Value_ 属性中。

更新更改

可以通过调用 DataWebServices Web 服务 _SubmitChangesAsync_ 方法来将 _SilverlightUpdateChanges_ 的数据更改提交到数据库。此方法需要更改的字典对象和表名。

_DataWebServices_ 项目使用 _UpdateChanges_ 类来提交数据库更改。 _UpdateChanges_ 构造函数不需要参数。 _UpdateChanges_ 类公开了 _SubmitChanges_ 方法,该方法提交要进行的更改。 _SubmitChanges_ 需要您要更改的数据的 LINQ _DatabaseContext_ 对象、表名以及更改的字典对象。

表名参数必须与 LINQ 类中分配的表名属性匹配。您可以通过查看 LINQ DBML 文件中的类定义来查看属性。它看起来会像这样

[Table(Name="dbo.Customers")]
Public partial class Customer : INotifyPropertyChanging, 
                                INotifyPropertyChanged

如果属性包含架构,则必须包含它。以下代码段调用 _SubmitChanges_ 方法,传递一个字典更改以更新 _dbo.Customers_ 表

NorthwindDataContext northwindDB = new NorthwindDataContext(connectionString);
//create a new UpdateChanges object and submit changes 
//to the Customers database using the northwindDB LINQ Northiwind provider
UpdateChanges testChanges = new UpdateChanges();
return (testChanges.SubmitChanges(northwindDB, 
        "dbo.Customers", changesDictionary));

_SubmitChanges_ 方法将对每个更改的记录执行 _UPDATE_ 语句。它每个记录只执行一个 _UPDATE_ 语句。如果您对给定记录进行了多次更改,它将在单个语句中合并这些更新。

_SubmitChanges_ 返回一个字符串,显示成功更新的数量和执行的语句数量。

_UpdateChanges_ 类还公开了以下属性

属性 描述
ErrorList 任何更新错误的异常列表。
SuccessCounter 成功更新语句的数量。
ExecutionCounter 执行更新语句的数量。
分隔符 键分隔符。默认为竖线 '|'。

问题

以当前形式使用这些类有一些优点和缺点。

优点

  • 只发送更改
  • 使用直接 _UPDATE_ 语句可实现快速数据库执行和最小的数据库流量

缺点

  • 不提供乐观锁定机制
  • 仅适用于 SQL Server
  • 当前仅支持更新 - 不支持删除或添加

删除记录相对容易。插入也可能,但需要实现一种返回新值键的机制。添加乐观锁定需要将部分或全部字段传回。

在 Silverlight 端构建 SQL 字符串并将其发送回 Web 服务的选项已在我脑海中闪过。这将解决许多问题。但是,安全问题很大;能够针对数据库执行任意 SQL 语句太危险了。加密过程和安全标识方案可能可以解决其中一些问题。

另一个问题是键分隔符,目前设置为竖线 |。如果任何被更改的属性包含此值,更新操作将失败。可以通过将其更改为不太显眼的值来轻松修复此问题(跟踪类和更新类都可以通过 _Delimiter_ 属性进行更改)。如果属性名与相应的表字段名不同,则无法正常工作。可以通过提供映射例程轻松修复此问题。

我正在考虑一个映射例程,用于将查找值映射到字段名,这将加快操作速度并传输更少的数据。

怪癖

在处理 Silverlight 2 时,我遇到了一些问题。其中之一是默认启动项目。人们会认为这是 Silverlight 项目,但实际上是 Web 服务。如果将 Silverlight 项目设为启动项目,在调试模式下(按 F5)启动项目时可能会出现通信错误。但是,在非调试模式(Ctrl-F5)下运行可以正常工作。

另一个是偶尔出现的警告

Custom tool warning: Unable to load one or more of the requested types. 
Retrieve the LoaderExceptions property for more information. 
G:\Data\VS\VS2008\Silverlight\SilverlightUpdateChanges\SilverlightUpdateChanges\
   Service References\NorthwindSvc\Reference.svcmap.

这似乎不影响执行,但很奇怪,并且可能与 Silverlight 的 Beta 状态有关。当 Visual Studio 重新启动时,它会消失。

结论

希望这能为从 Silverlight 更新数据库提供解决方案。我打算改进当前的功能,解决前面提到的一些问题。欢迎任何反馈,包括(建设性)批评。如果您打算评分低于 3,我将不胜感激。

历史

  • 2008 年 8 月 31 日 - 第一个版本。
© . All rights reserved.