关于VB6迁移的好消息

[原文作者]:Jason Zanders

[原文链接]:Good News for VB6 Migration

        今天我很高兴的宣布我们的VBRun开发中心对于VB6开发人员重新开放并且提供一个免费的合作伙伴的工具。如果你以前还没有访问过VBRun,它提供了最好的迁移策略,工具,支持信息和培训,这些都可以帮助你逐渐的从VB6转变到.NET。新开发中心最大的改变是添加了功能——合作伙伴页面提供和推荐免费的工具(下面详细介绍)。我们已经采取措施使VBRun和VBasic开发中心密切协作——通过增加VB6.0标签(链接到VBRun)马上就可以打开VBasic开发中心。

image

        VBRun第一个有特色的合作伙伴是Artinsoft和他们的工具Visual Basic Upgrade Companion [VBUC]。我们的合作带给你一个完全免费的企业版工具Artinsoft VBUC,它会支持10000行的代码转换针对每个解决方案(你可以购买更高级的产品版本去转换更多行的代码)。你可以将VBUC当做是VB Upgrade Wizard的演变工具,VB Upgrade Wizard是在Visual Studio 2008中或者更早发布的。在很多方面它是一个真正的Artinsoft帮助我们大约在2002的代码演变。

        下载免费版本的VBUC通过代码’MSDN

        VBUC工具的一些改进:

  • 大幅度减少的错误,警告和由于这个工具不能理解和升级代码引起的错误。我们的目标是更接近等价于你的VB6代码,以便你可以集中精力于适合业务的.NET代码的革新。当然没有迁移工具也可以100%迁移你的代码,也不需要你测试和一些其它的工作,但是这个工具将会帮你最大程度减少繁琐的工作。

image

  • 支持VS2010和9种语言——就像你期望的那样我们已经增加了对Visual Studio最好版本VS2010的全面的支持。而且我们认为在你使用Visual Studio时使用VBUC是很重要的。你可以获取到的VBUC版本支持英语,日语和德语。过几个星期,我们计划发布更新的版本支持中文简体和繁体,法语,西班牙语,意大利语和韩语。
  • 支持转化为VB.ET和C#——你可以选择最适合你的团队的语言。
  • 深层配置代码转换规则。Artinsoft包含迭代性质的迁移,而且他们提供了定制代码转换规则的功能。如果你想修改这些设置,查看工具中的配置管理。

image

  • 更好的控制后期绑定的代码。我们在使用后期绑定的代码时都是聪明或者内疚的,这取决于你如何看待它。不像以前的工具,这个版本可以检测后期绑定并且自动修复转换时的问题以及在.NET中的一些通用类型。
  • 功能评估——过去你不得不下载模式和评估工具或者和咨询顾问一起计算出在迁移方面有多少工作量。VBUC包含一个内置的评估工具将会给出一个关于代码,依赖关系,困难的控件/代码等等的报告。所以你可以更好的估算你的成本和风险。在这个工具中,按下F4就可以运行这个评估工具。有一件很酷的事情就是结果会以HTML和Excel电子表格的形式报告,所以很容易阅读和整理自己的报告。

        查看这个VBUC下载的页面获取更多的详细信息如何使用这个工具(记住填入’MSDN’去获取价值$799的免费版本)。

        我们期望继续上传最好的迁移策略,最好的新的合作伙伴的工具,支持的政策,更新Interop Forms Toolkit 2.1(添加.NET的窗口和用户控件到VB6应用)以及指导文档到VBRun 网站,所以你应该经常查看。还有一个很有活力的论坛VB6 migration and interop forum,那里有我们的团队和MVP的专家。

        如果你面对VB6迁移的挑战,希望你看一下这个网站并且找到对你有用的工具。

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中。存储过程可以由专门的数据库程序员在数据库上创建,这样应用程序的程序员甚至不会注意到有另外的三个表!这大大减少了应用程序的复杂性,并使它更容易维护。

        就是这些了。享受它吧!

避免在实现IOleCommandTarget接口时的自动化错误

[原文作者]:Jared Parsons

[原文链接]:Avoiding automation bugs when implementing IOleCommandTarget

        在Visual Studio 2010发布后不久,我想用新的VSIX格式为传统的包做扩展试验。我卸载了Visual Studio,通过运行新的包的项目向导。然而一个全新的项目,却有一个加载错误对话框出现。经过一番调查,我发现生成的项目文件被损坏。模板的代码多数不会被替代。

        这个bug让我有些不安,我立即发送了一封电子邮件给了相关组,并询问了有关情况。很快便得到了有关调试的回复。

        你有没有在机器上安装任何的扩展?

        快速检查后,我证实了我的机器上只有我自己VsVim项目的唯一一个扩展。我自然认为这不可能是任何VsVim故障,因为它不参与任何的项目创建以及没有其他项目类型受到影响。令人惊讶的是,卸载VsVim后,一切开始正常运行。在经过更多次的核实以后,让我感到有些羞愧,我意识到这确实是我的问题。

        这表明,包向导的项目文件替换和项目模板稍有不同。它用DTE object model做替换。DTE和其他的VSIP接口不同,因为它参与自动化(aka Macros)。

        例如,像TextSelection.Delete这样的操作不直接从缓冲区删除文本。它只是执行代表用户按下删除键的命令,最后由编辑器执行并删除所选内容。这些命令通过IOleCommandTarget链被处理。

        一旦知道了这个,那么我立刻就明白发生了什么事情。为了执行编辑,包向导不断地发送按键到缓冲区。VsVim用IOleCommandTarget接口拦截按键,并愉快地对待它们作为Vim命令。

        进一步的含意甚至比包向导更加深远。宏主要在DTE object model上操作。

        幸运的是有一个很简单的办法解决这个问题。Visual Studio提供了一个很好的方法判断目前你是否处在自动化的过程中。在IOleCommandTarget入口点处,增加一个简单的检查,就可以很好地解决这个问题了。

if (VsShellUtilities.IsInAutomationFunction(_serviceProvider))

{

        return false;

}

        通常情况下,在IOleCommandTarget链中的任何组件都应该在QueryStatus和Exec上做相同的检查。如果你的组件不是为宏和自动化特别设计的,那么在自动化过程中处理一条命令将会很难跟踪到问题。

去除XML 文本中的重命名空间

[原文作者]:VBTeam

[原文链接]:Removing duplicate namespaces in XML Literals (Shyam Namboodiripad)

        XML中的重命名空间问题是我们在处理XML文本和LINQ to XML API时的一个常见问题。看如下的实例。此代码导入了一个默认的XML命名空间——“Hello”.

Code:
Imports <xmlns="hello">
    Module Module1
        Sub Main()
            Dim x = <A>
                <%= <B></B> %>
                </A>
            Console.WriteLine("x:")
            Console.WriteLine(x)
            Dim y = <A></A>
            y.Add(<B></B>)
            Console.WriteLine("y:")
            Console.WriteLine(y)
        End Sub
End Module

Output:
x:
<A xmlns="hello">
  <B></B>
</A>
y:
<A xmlns="hello">
<B xmlns="hello"></B>
</A>

        如你所见,在Y的输出中,元素<B>有一个假的(重复的)XML命名空间声明。虽然单从技术上而言,这段XML是正确的(即根据XML 1.0 规范,它是合法的XML),但是有一些工具在处理包含重命名空间的XML的时候会遇到问题。

        比如说,看下面这段代码,我尝试加载一个MSBUILD项目文件(.vbproj),并修改它的内容。MSBUILD项目基本上和XML差不多,所以我能用VB XML文本来处理这样的文件。我将我代码中的默认XML命名空间设置成与MSBIULD文件里XML中的一致。 注意输出有之前同样的问题——我所加的代码<NoWarn>有一个重名的XML命名空间。

        假使我将这段代码生成的XML保存为一个”.vbproj”文件,并用MSBUILD来编译,操作会因为<NoWarn>元素的重命名空间而失败。换而言之,即使XML是合法的,MSBUILD不能处理包含重命名空间的XML.

Code:
Imports <xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    Module Module2
        Sub Main()
            Dim projectFile As String = "..\..\ConsoleApplication1.vbproj"
            Dim y = XDocument.Load(projectFile)
            Dim element = <NoWarn>42016</NoWarn>
            y.<Project>.<PropertyGroup>.First.Add(element)
            Console.WriteLine("y:")
            Console.WriteLine(y)
        End Sub
    End Module

Output:

y:
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>

    <NoWarn xmlns="420164201642016http://schemas.microsoft.com/developer/msbuild/2003">42016</NoWarn>
  </PropertyGroup>

</Project>

        VB为何允许包含重命名空间的XML生成?

        拿上面的第一段代码来说。你能从变量X的输出看出来,VB 编译器正确地指出了不必在代码<B>处声明命名空间。编译器能自己将这个解决。 这是因为结点<B>是语法意义上的内嵌在同一命名空间(即’Hello’)的<A>里的(用一个内嵌表达式)。

        然而,对变量Y来说,VB编译器很难知道结点<B>实际上是内嵌在结点<A>中的。为了知道这个,编译器需要检查代码流,并找出结点<B>被传给了一个定义在类型‘XElement’上的叫’Add’的函数, 它还要知道函数绑定的对象(即y)实际上已经含有一个已经在同一个命名空间(即 ‘Hello’)中的节点<A>。即使编译器足够聪明,能知道以上这些,它绝对不可能聪明到能解决那种(如上面的第二段示例代码所示)XML源代码甚至都不是程序的一部分的那种问题——这种可能在一些文件或者网络数据包中是存在的。

        因为编译器不知道结点<B>会在哪个文件里面结束,它就自动把它置于默认的命名空间文件里面了。(即”hello”)

        这样的话VB编译器就认不出来了。LINQ to XML API 却可以,不是吗?

        是的。LINQ to XML API能认出来,并移除重名的命名空间。但是它并不默认强制这样做。我想此中缘由是因为它并不是一个性能(即有个相关的性能专门检查每个结点以确认是否有重名的命名空间),毕竟,那样的XML是合法的(语法有点奇怪),即使它含有重命名空间——那如此说来,我们为何又常常强制检查额外的命名空间呢?

        在VS2010/.Net 4.0里面,LINQ to XML API提供了办法来暂时避免这个问题并生成更好看的XML. 你能加一个’annotation’到根Xelement/XDOcument结点以告诉API来避免重复的命名空间。看如下示例。API会逐个检查它添加的每个结点,移除任何不必要的重命名空间声明。

        相应地, 你能用’Saveoptions’/’ReaderOptions’ 以证明下面的示例。在这种情况下,XML会生成重命名空间,但是API会做一些额外的工作在保存XML的时候来移除重命名空间。

Code:
Imports <xmlns="hello">

    Module Module1 
      Sub Main() 
        Dim y = <A></A>
        y.AddAnnotation(SaveOptions.OmitDuplicateNamespaces)
        y.Add(&lt
;B></B>)
        y.Add(<C></C>)
        Console.WriteLine("y:")
        Console.WriteLine(y) 
      End Sub
    End Module

‘If you wish to save the XML to a file
Module Module2
    Sub Main()
        Dim y = <A></A>
        y.Add(<B></B>)
        y.Add(<C></C>)
        y.Save("out.xml", SaveOptions.OmitDuplicateNamespaces)
    End Sub
End Module

‘If you wish to create an XmlReader object for your XML
Module Module1 
    Sub Main()
        Dim y = <A></A>
        y.Add(<B></B>)
        y.Add(<C></C>)
        Dim reader =
        y.CreateReader(ReaderOptions.OmitDuplicateNamespaces)
        …
    End Sub
End Module

Output
y:
<A xmlns="hello">
  <B></B>
  <C></C>
</A>

希望这些能对你有所帮助。:)